From 48954e86178c5c3d95f64b59d9a88f22a51ff1ec Mon Sep 17 00:00:00 2001 From: Flex <q1406482700@163.com> Date: 星期四, 26 六月 2025 16:08:28 +0800 Subject: [PATCH] 修改校验文本错误以及功能补充 --- ruoyi-ui/apps/web-antd/src/views/work/Issued/index.vue | 300 +++++ ruoyi-ui/apps/web-antd/src/views/work/myWork/user-assign-work.vue | 170 +++ ruoyi-ui/apps/web-antd/src/adapter/component/index.ts | 4 ruoyi-ui/apps/web-antd/src/views/work/Issued/user-import-modal.vue | 108 ++ ruoyi-ui/apps/web-antd/src/views/work/myWork/index.vue | 303 +++++ ruoyi-ui/apps/web-antd/src/components/upload/src/new-file-upload.vue | 247 ++++ ruoyi-ui/packages/locales/src/langs/zh-CN/common.json | 1 ruoyi-ui/apps/web-antd/src/views/work/myWork/dept-tree.vue | 128 ++ ruoyi-ui/apps/web-antd/src/views/work/myWork/user-reset-pwd-modal.vue | 111 ++ ruoyi-ui/apps/web-antd/src/views/work/Issued/user-info-modal.vue | 62 + ruoyi-ui/apps/web-antd/src/views/work/Issued/user-drawer.vue | 213 +++ ruoyi-ui/apps/web-antd/src/views/work/Issued/dept-tree.vue | 128 ++ ruoyi-ui/apps/web-antd/src/views/work/myWork/data.tsx | 262 ++++ ruoyi-ui/apps/web-antd/src/views/work/myWork/user-drawer.vue | 218 ++++ ruoyi-ui/packages/@core/base/shared/src/constants/dict-enum.ts | 3 ruoyi-ui/apps/web-antd/src/views/work/myWork/info.tsx | 129 ++ ruoyi-ui/apps/web-antd/src/locales/langs/en-US/pages.json | 4 ruoyi-ui/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue | 2 ruoyi-ui/packages/locales/src/langs/en-US/common.json | 1 ruoyi-ui/apps/web-antd/src/components/upload/index.ts | 1 ruoyi-ui/apps/web-antd/src/views/work/myWork/user-report.vue | 176 +++ ruoyi-ui/apps/web-antd/src/locales/langs/zh-CN/pages.json | 4 ruoyi-ui/apps/web-antd/src/views/work/Issued/user-reset-pwd-modal.vue | 111 ++ ruoyi-ui/apps/web-antd/src/views/work/myWork/user-import-modal.vue | 108 ++ ruoyi-ui/apps/web-antd/src/views/work/Issued/data.tsx | 233 ++++ ruoyi-ui/apps/web-antd/src/views/work/myWork/user-info-modal.vue | 62 + ruoyi-ui/apps/web-antd/src/views/work/Issued/info.tsx | 129 ++ 27 files changed, 3,198 insertions(+), 20 deletions(-) diff --git a/ruoyi-ui/apps/web-antd/src/adapter/component/index.ts b/ruoyi-ui/apps/web-antd/src/adapter/component/index.ts index c61e9b4..f8f783f 100644 --- a/ruoyi-ui/apps/web-antd/src/adapter/component/index.ts +++ b/ruoyi-ui/apps/web-antd/src/adapter/component/index.ts @@ -39,7 +39,7 @@ } from 'ant-design-vue'; import { Tinymce as RichTextarea } from '#/components/tinymce'; -import { FileUpload, ImageUpload } from '#/components/upload'; +import { FileUpload, ImageUpload, NewFileUpload } from '#/components/upload'; const withDefaultPlaceholder = <T extends Component>( component: T, @@ -92,6 +92,7 @@ | 'DefaultButton' | 'Divider' | 'FileUpload' + | 'NewFileUpload' | 'IconPicker' | 'ImageUpload' | 'Input' @@ -193,6 +194,7 @@ Upload, ImageUpload, FileUpload, + NewFileUpload, RichTextarea, }; diff --git a/ruoyi-ui/apps/web-antd/src/components/upload/index.ts b/ruoyi-ui/apps/web-antd/src/components/upload/index.ts index b86d495..6ca877d 100644 --- a/ruoyi-ui/apps/web-antd/src/components/upload/index.ts +++ b/ruoyi-ui/apps/web-antd/src/components/upload/index.ts @@ -1,2 +1,3 @@ export { default as FileUpload } from './src/file-upload.vue'; +export { default as NewFileUpload } from './src/new-file-upload.vue'; export { default as ImageUpload } from './src/image-upload.vue'; diff --git a/ruoyi-ui/apps/web-antd/src/components/upload/src/new-file-upload.vue b/ruoyi-ui/apps/web-antd/src/components/upload/src/new-file-upload.vue new file mode 100644 index 0000000..f7debc2 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/components/upload/src/new-file-upload.vue @@ -0,0 +1,247 @@ +<!-- 杩欐槸涓�涓敤鏉ヨ嚜瀹氫箟鍖栫殑鏂囦欢涓婁紶缁勪欢 --> +<script lang="ts" setup> +import type { UploadFile, UploadProps } from 'ant-design-vue'; +import type { UploadRequestOption } from 'ant-design-vue/lib/vc-upload/interface'; + +import type { AxiosResponse } from '@vben/request'; + +import type { AxiosProgressEvent } from '#/api'; + +import { ref, toRefs, watch } from 'vue'; + +import { $t } from '@vben/locales'; + +import { UploadOutlined } from '@ant-design/icons-vue'; +import { message, Upload } from 'ant-design-vue'; +import { isArray, isFunction, isObject, isString } from 'lodash-es'; + +import { uploadApi } from '#/api'; + +import { checkFileType } from './helper'; +import { UploadResultStatus } from './typing'; +import { useUploadType } from './use-upload'; + +defineOptions({ name: 'FileUpload', inheritAttrs: false }); + +const props = withDefaults( + defineProps<{ + /** + * 寤鸿浣跨敤鎷撳睍鍚�(涓嶅甫.) + * 鎴栬�呮枃浠跺ご image/png绛�(娴嬭瘯鍒ゆ柇涓嶅噯纭�) 涓嶆敮鎸乮mage/*绫讳技鐨勫啓娉� + * 闇�鑷鏀归�� ./helper/checkFileType鏂规硶 + */ + accept?: string[]; + api?: ( + file: Blob | File, + onUploadProgress?: AxiosProgressEvent, + ) => Promise<AxiosResponse<any>>; + disabled?: boolean; + helpText?: string; + // 鏈�澶ф暟閲忕殑鏂囦欢锛孖nfinity涓嶉檺鍒� + maxNumber?: number; + // 鏂囦欢鏈�澶у灏慚B + maxSize?: number; + // 鏄惁鏀寔澶氶�� + multiple?: boolean; + // support xxx.xxx.xx + // 杩斿洖鐨勫瓧娈� 榛樿url + resultField?: 'fileName' | 'ossId' | 'url' | string; + /** + * 鏄惁鏄剧ず涓嬮潰鐨勬弿杩� + */ + showDescription?: boolean; + value?: string[]; + }>(), + { + value: () => [], + disabled: false, + helpText: '', + maxSize: 2, + maxNumber: 1, + accept: () => [], + multiple: false, + api: uploadApi, + resultField: '', + showDescription: true, + }, +); +const emit = defineEmits(['change', 'update:value', 'delete', 'startUpload', 'EndUpload']); +const { accept, helpText, maxNumber, maxSize } = toRefs(props); +const isInnerOperate = ref<boolean>(false); +const { getStringAccept } = useUploadType({ + acceptRef: accept, + helpTextRef: helpText, + maxNumberRef: maxNumber, + maxSizeRef: maxSize, +}); + +const fileList = ref<UploadProps['fileList']>([]); +const isLtMsg = ref<boolean>(true); +const isActMsg = ref<boolean>(true); +const isFirstRender = ref<boolean>(true); + +watch( + () => props.value, + (v) => { + if (isInnerOperate.value) { + isInnerOperate.value = false; + return; + } + let value: string[] = []; + if (v) { + if (isArray(v)) { + value = v; + } else { + value.push(v); + } + fileList.value = value.map((item, i) => { + if (item && isString(item)) { + return { + uid: `${-i}`, + name: item.slice(Math.max(0, item.lastIndexOf('/') + 1)), + status: 'done', + url: item, + }; + } else if (item && isObject(item)) { + return item; + } + return null; + }) as UploadProps['fileList']; + } + if (!isFirstRender.value) { + emit('change', value); + isFirstRender.value = false; + } + }, + { + immediate: true, + deep: true, + }, +); + +const handleRemove = async (file: UploadFile) => { + if (fileList.value) { + const index = fileList.value.findIndex((item) => item.uid === file.uid); + index !== -1 && fileList.value.splice(index, 1); + const value = getValue(); + isInnerOperate.value = true; + emit('update:value', value); + emit('change', value); + } + emit('delete', file); +}; + +const beforeUpload = async (file: File) => { + const { maxSize, accept } = props; + const isAct = await checkFileType(file, accept); + if (!isAct) { + message.error($t('component.upload.acceptUpload', [accept])); + isActMsg.value = false; + // 闃叉寮瑰嚭澶氫釜閿欒鎻愮ず + setTimeout(() => (isActMsg.value = true), 1000); + } + const isLt = file.size / 1024 / 1024 > maxSize; + if (isLt) { + message.error($t('component.upload.maxSizeMultiple', [maxSize])); + isLtMsg.value = false; + // 闃叉寮瑰嚭澶氫釜閿欒鎻愮ず + setTimeout(() => (isLtMsg.value = true), 1000); + } + return (isAct && !isLt) || Upload.LIST_IGNORE; +}; + +async function customRequest(info: UploadRequestOption<any>) { + const { api } = props; + if (!api || !isFunction(api)) { + console.warn('upload api must exist and be a function'); + return; + } + emit( 'startUpload' ) + try { + // 杩涘害鏉′簨浠� + const progressEvent: AxiosProgressEvent = (e) => { + const percent = Math.trunc((e.loaded / e.total!) * 100); + info.onProgress!({ percent }); + }; + const res = await api?.(info.file as File, progressEvent); + /** + * 鐢眊etValue澶勭悊 浼犲璞¤繃鍘� + * 鐩存帴浼爏tring(id)浼氳杞负Number + * 鍐呴儴鐨勯�昏緫鐢眗equestClient.upload澶勭悊 杩欓噷涓嶇敤鍒ゆ柇涓氬姟鐘舵�佺爜 涓嶇鍚堜細鑷姩reject + */ + info.onSuccess!(res); + message.success($t('component.upload.uploadSuccess')); + // 鑾峰彇 + const value = getValue(); + isInnerOperate.value = true; + emit('update:value', value); + emit('change', value); + } catch (error: any) { + console.error(error); + info.onError!(error); + } finally { + emit( 'EndUpload' ) + } +} + +function getValue() { + const list = (fileList.value || []) + .filter((item) => item?.status === UploadResultStatus.DONE) + .map((item: any) => { + if (item?.response && props?.resultField) { + return item?.response?.[props.resultField]; + } + // 閫傜敤浜庡凡缁忔湁鍥剧墖 鍥炴樉鐨勬儏鍐� 浼氶粯璁ゅ湪init澶勭悊涓簕url: 'xx'} + if (item?.url) { + return item.url; + } + // 娉ㄦ剰杩欓噷鍙栫殑key涓� url + return item?.response?.url; + }); + return list; +} +</script> + +<template> + <div> + <Upload + v-bind="$attrs" + v-model:file-list="fileList" + :accept="getStringAccept" + :before-upload="beforeUpload" + :custom-request="customRequest" + :disabled="disabled" + :max-count="maxNumber" + :multiple="multiple" + list-type="text" + :progress="{ showInfo: true }" + @remove="handleRemove" + > + <div v-if="fileList && fileList.length < maxNumber"> + <a-button> + <UploadOutlined /> + {{ $t('component.upload.upload') }} + </a-button> + </div> + <div v-if="showDescription" class="mt-2 flex flex-wrap items-center"> + 璇蜂笂浼犱笉瓒呰繃 + <div class="text-primary mx-1 font-bold">{{ maxSize }}MB</div> + 鐨� + <div class="text-primary mx-1 font-bold">{{ accept.join('/') }}</div> + 鏍煎紡鏂囦欢 + </div> + </Upload> + </div> +</template> + +<style> +.ant-upload-select-picture-card i { + font-size: 32px; + color: #999; +} + +.ant-upload-select-picture-card .ant-upload-text { + margin-top: 8px; + color: #666; +} +</style> diff --git a/ruoyi-ui/apps/web-antd/src/locales/langs/en-US/pages.json b/ruoyi-ui/apps/web-antd/src/locales/langs/en-US/pages.json index ea66e51..1df7c96 100644 --- a/ruoyi-ui/apps/web-antd/src/locales/langs/en-US/pages.json +++ b/ruoyi-ui/apps/web-antd/src/locales/langs/en-US/pages.json @@ -1,7 +1,11 @@ { "common": { "add": "Add", + "WorkIssued": "WorkIssued", "edit": "Edit", + "look": "Look", + "report":"report", + "AssigningWork":"Assigning Work", "delete": "Delete", "more": "More", "search": "Search", diff --git a/ruoyi-ui/apps/web-antd/src/locales/langs/zh-CN/pages.json b/ruoyi-ui/apps/web-antd/src/locales/langs/zh-CN/pages.json index 1bff1bb..80bc598 100644 --- a/ruoyi-ui/apps/web-antd/src/locales/langs/zh-CN/pages.json +++ b/ruoyi-ui/apps/web-antd/src/locales/langs/zh-CN/pages.json @@ -1,7 +1,11 @@ { "common": { "add": "鏂板", + "WorkIssued": "宸ヤ綔涓嬪彂", "edit": "缂栬緫", + "look": "鏌ョ湅", + "report":"姹囨姤", + "AssigningWork":"宸ヤ綔涓嬪彂", "delete": "鍒犻櫎", "more": "鏇村", "search": "鎼滅储", diff --git a/ruoyi-ui/apps/web-antd/src/views/work/Issued/data.tsx b/ruoyi-ui/apps/web-antd/src/views/work/Issued/data.tsx new file mode 100644 index 0000000..53e7c8e --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/Issued/data.tsx @@ -0,0 +1,233 @@ +import type { UploadFile, UploadProps } from 'ant-design-vue'; +import type { FormSchemaGetter } from '#/adapter/form'; +import type { VxeGridProps } from '#/adapter/vxe-table'; + +import { DictEnum } from '@vben/constants'; +import { getPopupContainer } from '@vben/utils'; +import { useAppConfig } from '@vben/hooks'; +import { useAccessStore } from '@vben/stores'; +import { ref } from 'vue'; +import { getDictOptions } from '#/utils/dict'; +// import { OptionsItem } from "" + +export const querySchema: FormSchemaGetter = () => [ + { + component: 'Input', + fieldName: 'workName', + label: '宸ヤ綔鍚嶇О', + }, + { + component: 'Input', + fieldName: 'id', + label: '缂栧彿', + }, + { + component: 'Input', + fieldName: 'responsibleDepartment', + label: '璐熻矗閮ㄩ棬', + }, + { + component: 'Select', + componentProps: { + getPopupContainer, + options: getDictOptions(DictEnum.TASK_STATUS), + }, + fieldName: 'taskStatus', + label: '浠诲姟鐘舵��', + }, + { + component: 'Select', + componentProps: { + getPopupContainer, + options: getDictOptions(DictEnum.ASSIGNMENT_STATUS), + }, + fieldName: 'assignmentStatus', + label: '鍒嗛厤鐘舵��', + }, + { + component: 'Input', + fieldName: 'Annual', + label: '骞村害', + }, +]; + +export const columns: VxeGridProps['columns'] = [ + { type: 'checkbox', width: 60 }, + { + field: 'workName', + title: '鍚嶇О', + }, + { + field: 'id', + title: '缂栧彿', + }, + { + field: 'responsibleDepartment', + title: '璐熻矗閮ㄩ棬', + }, + { + field: 'Head', + title: '璐熻矗浜�', + }, + { + field: 'workClass', + title: '绫诲埆', + }, + { + field: 'assessmentTime', + title: '鑰冩牳鏃堕棿', + }, + { + field: 'taskStatus', + title: '浠诲姟鐘舵��', + }, + { + field: 'assignmentStatus', + title: '鍒嗛厤鐘舵��', + }, + { + field: 'Annual', + title: '骞村害', + }, + { + field: 'workProgress', + title: '宸ヤ綔杩涘害', + }, + { + field: 'action', + fixed: 'right', + slots: { default: 'action' }, + title: '鎿嶄綔', + width: 180, + }, +]; +// 琛ㄥ崟 +export const drawerSchema: FormSchemaGetter = () => [ + { + component: 'Input', + fieldName: 'workName', + label: '宸ヤ綔鍚嶇О', + rules: 'required' + }, + { + component: 'Input', + fieldName: 'workClass', + label: '宸ヤ綔绫诲埆', + rules: 'required', + }, + { + component: 'Textarea', + fieldName: 'workContent', + label: '宸ヤ綔鍐呭' + }, + + { + component: 'Input', + fieldName: 'projectBudget', + label: '椤圭洰棰勭畻%', + defaultValue: undefined + }, + { + component: 'Input', + fieldName: 'amountProject', + defaultValue: undefined, + label: '椤圭洰閲戦' + }, + { + component: 'Select', + componentProps: { + getPopupContainer, + options: [] + }, + fieldName: 'responsibleDepartment', + label: '璐熻矗閮ㄩ棬', + rules: 'required', + }, + { + component: 'Select', + componentProps: { + getPopupContainer, + options: getDictOptions(DictEnum.SYS_NORMAL_DISABLE) + }, + fieldName: 'Head', + label: '璐熻矗浜�', + }, + { + component: 'DatePicker', + componentProps: { + format: 'YYYY', + showTime: true, + valueFormat: 'YYYY', + picker: "year", + getPopupContainer, + }, + fieldName: 'Annual', + label: '骞村害', + rules: 'required', + }, + { + component: 'DatePicker', + componentProps: { + format: 'YYYY-MM-DD', + showTime: true, + valueFormat: 'YYYY-MM-DD', + getPopupContainer, + }, + fieldName: 'assessmentTime', + label: '鑰冩牳鏃堕棿', + rules: 'required', + }, + { + component: 'Select', + fieldName: 'assessmentIndicators', + formItemClass: 'items-baseline', + label: '鑰冩牳鎸囨爣', + }, + { + fieldName: 'File', + component: 'NewFileUpload', + componentProps: { + action: uploadUrl, + headers: headers, + change: handleChange, + maxSize: 20, + onDelete:(file: UploadFile)=>{ + console.log("瀛樺湪鏂囦欢琚垹闄�",file) + }, + onStartUpload:()=>{ + //寮�濮嬩笂浼� + UploadFileState.value = true + }, + onEndUpload:()=>{ + //涓婁紶缁撴潫 + UploadFileState.value = false + } + }, + formItemClass: 'items-baseline', + label: '鏂囦欢涓婁紶', + } +]; + +/* + 褰撳墠琚笂浼犳枃浠剁殑鏂囦欢鐘舵�� + true 鈥斺�� 姝e湪涓婁紶 + false 鈥斺�� 涓婁紶缁撴潫 +*/ +export const UploadFileState = ref(false) + +const { apiURL, clientId } = useAppConfig( + import.meta.env, + import.meta.env.PROD, +); +const uploadUrl = `${apiURL}/knowledge/attach/upload`; +const accessStore = useAccessStore(); +const headers = { + Authorization: `Bearer ${accessStore.accessToken}`, + clientId, +}; + +// 鏂囦欢閫夊彇 +function handleChange(res:any) { + console.log("閫夊彇浜嗘枃浠�") + console.log(res) +} diff --git a/ruoyi-ui/apps/web-antd/src/views/work/Issued/dept-tree.vue b/ruoyi-ui/apps/web-antd/src/views/work/Issued/dept-tree.vue new file mode 100644 index 0000000..df9d6c2 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/Issued/dept-tree.vue @@ -0,0 +1,128 @@ +<script setup lang="ts"> +import type { PropType } from 'vue'; + +import type { DeptTree } from '#/api/system/user/model'; + +import { onMounted, ref } from 'vue'; + +import { SyncOutlined } from '@ant-design/icons-vue'; +import { Empty, InputSearch, Skeleton, Tree } from 'ant-design-vue'; + +import { getDeptTree } from '#/api/system/user'; + +defineOptions({ inheritAttrs: false }); + +withDefaults(defineProps<{ showSearch?: boolean }>(), { showSearch: true }); + +const emit = defineEmits<{ + /** + * 鐐瑰嚮鍒锋柊鎸夐挳鐨勪簨浠� + */ + reload: []; + /** + * 鐐瑰嚮鑺傜偣鐨勪簨浠� + */ + select: []; +}>(); + +const selectDeptId = defineModel('selectDeptId', { + required: true, + type: Array as PropType<string[]>, +}); + +const searchValue = defineModel('searchValue', { + type: String, + default: '', +}); + +/** 閮ㄩ棬鏁版嵁婧� */ +type DeptTreeArray = DeptTree[]; +const deptTreeArray = ref<DeptTreeArray>([]); +/** 楠ㄦ灦灞忓姞杞� */ +const showTreeSkeleton = ref<boolean>(true); + +async function loadTree() { + showTreeSkeleton.value = true; + searchValue.value = ''; + selectDeptId.value = []; + + const ret = await getDeptTree(); + console.log( "ret", ret ) + deptTreeArray.value = ret; + showTreeSkeleton.value = false; +} + +async function handleReload() { + await loadTree(); + emit('reload'); +} + +onMounted(loadTree); +</script> + +<template> + <div :class="$attrs.class"> + <Skeleton + :loading="showTreeSkeleton" + :paragraph="{ rows: 8 }" + active + class="p-[8px]" + > + <div + class="bg-background flex h-full flex-col overflow-y-auto rounded-lg" + > + <!-- 鍥哄畾鍦ㄩ《閮� 蹇呴』鍔犱笂bg-background鑳屾櫙鑹� 鍚﹀垯浼氫骇鐢�'绌块��'鏁堟灉 --> + <div + v-if="showSearch" + class="bg-background z-100 sticky left-0 top-0 p-[8px]" + > + <InputSearch + v-model:value="searchValue" + :placeholder="$t('pages.common.search')" + size="small" + > + <template #enterButton> + <a-button @click="handleReload"> + <SyncOutlined class="text-primary" /> + </a-button> + </template> + </InputSearch> + </div> + <div class="h-full overflow-x-hidden px-[8px]"> + <Tree + v-bind="$attrs" + v-if="deptTreeArray.length > 0" + v-model:selected-keys="selectDeptId" + :class="$attrs.class" + :field-names="{ title: 'label', key: 'id' }" + :show-line="{ showLeafIcon: false }" + :tree-data="deptTreeArray" + :virtual="false" + default-expand-all + @select="$emit('select')" + > + <template #title="{ label }"> + <span v-if="label.indexOf(searchValue) > -1"> + {{ label.substring(0, label.indexOf(searchValue)) }} + <span style="color: #f50">{{ searchValue }}</span> + {{ + label.substring( + label.indexOf(searchValue) + searchValue.length, + ) + }} + </span> + <span v-else>{{ label }}</span> + </template> + </Tree> + <!-- 浠呮湰浜烘暟鎹潈闄� 鍙互鑰冭檻鐩存帴涓嶆樉绀� --> + <div v-else class="mt-5"> + <Empty + :image="Empty.PRESENTED_IMAGE_SIMPLE" + description="鏃犻儴闂ㄦ暟鎹�" + /> + </div> + </div> + </div> + </Skeleton> + </div> +</template> diff --git a/ruoyi-ui/apps/web-antd/src/views/work/Issued/index.vue b/ruoyi-ui/apps/web-antd/src/views/work/Issued/index.vue index d884f8b..9e2834d 100644 --- a/ruoyi-ui/apps/web-antd/src/views/work/Issued/index.vue +++ b/ruoyi-ui/apps/web-antd/src/views/work/Issued/index.vue @@ -1,13 +1,295 @@ -<template> - <div>宸ヤ綔涓嬪彂</div> -</template> +<!-- 宸ヤ綔鍒楄〃 --> +<script setup lang="ts"> +import type { VbenFormProps } from '@vben/common-ui'; -<script> -export default { - name: "index" +import type { VxeGridProps } from '#/adapter/vxe-table'; +import type { User } from '#/api/system/user/model'; + +import { ref } from 'vue'; + +import { useAccess } from '@vben/access'; +import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui'; +import { $t } from '@vben/locales'; +import { preferences } from '@vben/preferences'; +import { getVxePopupContainer } from '@vben/utils'; + +import { + Avatar, + Dropdown, + Menu, + MenuItem, + Modal, + Popconfirm, + Space, +} from 'ant-design-vue'; + +import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table'; +import { + userExport, + userList, + userRemove, + userStatusChange, +} from '#/api/system/user'; +import { TableSwitch } from '#/components/table'; +import { commonDownloadExcel } from '#/utils/file/download'; + +import { columns, querySchema } from './data'; +import userDrawer from './user-drawer.vue'; +import userImportModal from './user-import-modal.vue'; +import userInfoModal from './user-info-modal.vue'; +import userResetPwdModal from './user-reset-pwd-modal.vue'; + +/** + * 瀵煎叆 + */ +const [UserImpotModal, userImportModalApi] = useVbenModal({ + connectedComponent: userImportModal, +}); + +function handleImport() { + userImportModalApi.open(); } + +// 宸﹁竟閮ㄩ棬鐢� +const selectDeptId = ref<string[]>([]); + +const formOptions: VbenFormProps = { + schema: querySchema(), + commonConfig: { + labelWidth: 80, + componentProps: { + allowClear: true, + }, + }, + wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4', + handleReset: async () => { + selectDeptId.value = []; + + const { formApi, reload } = tableApi; + await formApi.resetForm(); + const formValues = formApi.form.values; + formApi.setLatestSubmissionValues(formValues); + await reload(formValues); + }, + // 鏃ユ湡閫夋嫨鏍煎紡鍖� + fieldMappingTime: [ + [ + 'createTime', + ['params[beginTime]', 'params[endTime]'], + ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'], + ], + ], +}; + +const gridOptions: VxeGridProps = { + checkboxConfig: { + // 楂樹寒 + highlight: true, + // 缈婚〉鏃朵繚鐣欓�変腑鐘舵�� + reserve: true, + // 鐐瑰嚮琛岄�変腑 + trigger: 'default', + checkMethod: ({ row }) => row?.userId !== 1, + }, + columns, + height: 'auto', + keepSource: true, + pagerConfig: {}, + proxyConfig: { + ajax: { + // 鑾峰彇椤甸潰鏁版嵁锛屾悳绱紝閲嶇疆涔熸槸 + /* + @params page:椤电爜鍙傛暟 + @params formValues:琛ㄥ崟鍙傛暟 + */ + query: async ({ page }, formValues = {}) => { + console.log("鑾峰彇椤甸潰鏁版嵁锛�") + const res = { + rows:[ { + id:"0", //宸ヤ綔id锛岀紪鍙� + workName:"宸ヤ綔鍚嶇О1", //宸ヤ綔鍚嶇О + workClass:"宸ヤ綔绫诲埆", //宸ヤ綔绫诲埆 + workContent:"宸ヤ綔鍐呭", //宸ヤ綔鍐呭 + projectBudget:"椤圭洰棰勭畻", //椤圭洰棰勭畻 + amountProject:"椤圭洰閲戦", //椤圭洰閲戦 + responsibleDepartment:"璐熻矗閮ㄩ棬", //璐熻矗閮ㄩ棬 + Head:"璐熻矗浜�", //璐熻矗浜� + Annual:"骞村害", //骞村害 + assessmentTime:"鑰冩牳鏃堕棿",//鑰冩牳鏃堕棿 + assessmentIndicators:"鑰冩牳鎸囨爣", //鑰冩牳鎸囨爣 + File:"鏂囦欢鍦板潃", //鏂囦欢涓婁紶 + taskStatus:"浠诲姟鐘舵��", //浠诲姟鐘舵�� + assignmentStatus:"鍒嗛厤鐘舵��", //鍒嗛厤鐘舵�� + workProgress:"宸ヤ綔杩涘害" //宸ヤ綔杩涘害 + } ], + total:1 + } + console.log( "res", res) + return res + }, + }, + }, + rowConfig: { + keyField: 'userId', + height: 48, + }, + id: 'system-user-index', +}; +const [BasicTable, tableApi] = useVbenVxeGrid({ + formOptions, + gridOptions, +}); + +const [UserDrawer, userDrawerApi] = useVbenDrawer({ + connectedComponent: userDrawer, +}); +// 宸ヤ綔涓嬪彂 +function handleAdd() { + userDrawerApi.setData({}); + userDrawerApi.open(); +} +// 缂栬緫 +function handleEdit(row: any) { + userDrawerApi.setData({ id: row.id }); + userDrawerApi.open(); +} +// 鏌ョ湅 +function handleLook(row: any) { + userDrawerApi.setData({ id: row.id, look: true }); + userDrawerApi.open(); +} +// 鍒犻櫎 +async function handleDelete(row: User) { + await userRemove([row.userId]); + await tableApi.query(); +} + +function handleMultiDelete() { + const rows = tableApi.grid.getCheckboxRecords(); + const ids = rows.map((row: User) => row.userId); + Modal.confirm({ + title: '鎻愮ず', + okType: 'danger', + content: `纭鍒犻櫎閫変腑鐨�${ids.length}鏉¤褰曞悧锛焋, + onOk: async () => { + await userRemove(ids); + await tableApi.query(); + }, + }); +} + +function handleDownloadExcel() { + commonDownloadExcel(userExport, '鐢ㄦ埛绠$悊', tableApi.formApi.form.values, { + fieldMappingTime: formOptions.fieldMappingTime, + }); +} + +const [UserInfoModal, userInfoModalApi] = useVbenModal({ + connectedComponent: userInfoModal, +}); +function handleUserInfo(row: User) { + userInfoModalApi.setData({ userId: row.userId }); + userInfoModalApi.open(); +} + +const [UserResetPwdModal, userResetPwdModalApi] = useVbenModal({ + connectedComponent: userResetPwdModal, +}); + +function handleResetPwd(record: User) { + userResetPwdModalApi.setData({ record }); + userResetPwdModalApi.open(); +} + +const { hasAccessByCodes } = useAccess(); </script> -<style scoped> - -</style> +<template> + <Page :auto-content-height="true"> + <div class="flex h-full gap-[8px]"> + <BasicTable class="flex-1 overflow-hidden" table-title="宸ヤ綔鍒楄〃"> + <template #toolbar-tools> + <Space> + <!-- 瀵煎嚭 --> + <!-- <a-button + @click="handleDownloadExcel" + > + {{ $t('pages.common.export') }} + </a-button> --> + <!-- 瀵煎叆 --> + <!-- <a-button + v-access:code="['system:user:import']" + @click="handleImport" + > + {{ $t('pages.common.import') }} + </a-button> --> + <!-- 鍒犻櫎 --> + <a-button + :disabled="!vxeCheckboxChecked(tableApi)" + danger + type="primary" + @click="handleMultiDelete" + > + {{ $t('pages.common.delete') }} + </a-button> + <!-- 鏂板 --> + <a-button + type="primary" + @click="handleAdd" + > + {{ $t('pages.common.WorkIssued') }} + </a-button> + </Space> + </template> + <template #avatar="{ row }"> + <!-- 鍙兘瑕佸垽鏂┖瀛楃涓叉儏鍐� 鎵�浠ユ病鏈変娇鐢�?? --> + <Avatar :src="row.avatar || preferences.app.defaultAvatar" /> + </template> + <template #status="{ row }"> + <TableSwitch + v-model="row.status" + :api="() => userStatusChange(row)" + :disabled=" + row.userId === 1 || !hasAccessByCodes(['system:user:edit']) + " + :reload="() => tableApi.query()" + /> + </template> + <template #action="{ row }"> + <Space> + <!-- 鏌ョ湅 --> + <ghost-button + @click.stop="handleLook(row)" + > + {{ $t('pages.common.look') }} + </ghost-button> + <!-- 缂栬緫 --> + <ghost-button + @click.stop="handleEdit(row)" + > + {{ $t('pages.common.edit') }} + </ghost-button> + <!-- 鍒犻櫎 --> + <Popconfirm + :get-popup-container="getVxePopupContainer" + placement="left" + title="纭鍒犻櫎锛�" + @confirm="handleDelete(row)" + > + <ghost-button + danger + @click.stop="" + > + {{ $t('pages.common.delete') }} + </ghost-button> + </Popconfirm> + </Space> + </template> + </BasicTable> + </div> + <UserImpotModal @reload="tableApi.query()" /> + <UserDrawer @reload="tableApi.query()" /> + <UserInfoModal /> + <UserResetPwdModal /> + </Page> +</template> diff --git a/ruoyi-ui/apps/web-antd/src/views/work/Issued/info.tsx b/ruoyi-ui/apps/web-antd/src/views/work/Issued/info.tsx new file mode 100644 index 0000000..cd00bb8 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/Issued/info.tsx @@ -0,0 +1,129 @@ +import type { DescItem } from '#/components/description'; + +import { DictEnum } from '@vben/constants'; + +import { Tag } from 'ant-design-vue'; +import dayjs from 'dayjs'; +import duration from 'dayjs/plugin/duration'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +import { renderDict } from '#/utils/render'; + +dayjs.extend(duration); +dayjs.extend(relativeTime); + +function renderTags(list: string[]) { + return ( + <div class="flex flex-row flex-wrap gap-0.5"> + {list.map((item) => ( + <Tag key={item}>{item}</Tag> + ))} + </div> + ); +} + +export const descSchema: DescItem[] = [ + { + field: 'userId', + label: '鐢ㄦ埛ID', + }, + { + field: 'status', + label: '鐢ㄦ埛鐘舵��', + render(value) { + return renderDict(value, DictEnum.SYS_NORMAL_DISABLE); + }, + }, + { + field: 'nickName', + label: '鐢ㄦ埛淇℃伅', + render(_, data) { + const { deptName = '鏆傛棤閮ㄩ棬淇℃伅', nickName, userName } = data; + // 涓轰簡鍏煎鏂扮増鏈拰鏃х増鏈� + let currentDept = deptName; + if (data.dept && data.dept.deptName) { + currentDept = data.dept.deptName; + } + return `${userName} / ${nickName} / ${currentDept}`; + }, + }, + { + field: 'phonenumber', + label: '鎵嬫満鍙�', + render(value) { + return value || '鏈缃墜鏈哄彿鐮�'; + }, + }, + { + field: 'email', + label: '閭', + render(value) { + return value || '鏈缃偖绠卞湴鍧�'; + }, + }, + { + field: 'postNames', + label: '宀椾綅', + render(value) { + if (Array.isArray(value) && value.length === 0) { + return '鏆傛棤淇℃伅'; + } + return renderTags(value); + }, + }, + { + field: 'roleNames', + label: '鏉冮檺', + render(value) { + if (Array.isArray(value) && value.length === 0) { + return '鏆傛棤淇℃伅'; + } + return renderTags(value); + }, + }, + { + field: 'createTime', + label: '鍒涘缓鏃堕棿', + }, + { + field: 'loginIp', + label: '涓婃鐧诲綍IP', + render(value) { + return value || <span class="text-orange-500">浠庢湭鐧诲綍杩�</span>; + }, + }, + { + field: 'loginDate', + label: '涓婃鐧诲綍鏃堕棿', + render(value) { + if (!value) { + return <span class="text-orange-500">浠庢湭鐧诲綍杩�</span>; + } + // 榛樿en鏄剧ず + dayjs.locale('zh-cn'); + // 璁$畻鐩稿樊绉掓暟 + const diffSeconds = dayjs().diff(dayjs(value), 'second'); + /** + * 杞负鏃堕棿鏄剧ず(x鏈� x澶�) + * https://dayjs.fenxianglu.cn/category/duration.html#%E4%BA%BA%E6%80%A7%E5%8C%96 + * + */ + const diffText = dayjs.duration(diffSeconds, 'seconds').humanize(); + return ( + <div class="flex gap-2"> + {value} + <Tag bordered={false} color="cyan"> + {diffText}鍓� + </Tag> + </div> + ); + }, + }, + { + field: 'remark', + label: '澶囨敞', + render(value) { + return value || '鏃�'; + }, + }, +]; diff --git a/ruoyi-ui/apps/web-antd/src/views/work/Issued/user-drawer.vue b/ruoyi-ui/apps/web-antd/src/views/work/Issued/user-drawer.vue new file mode 100644 index 0000000..0a24976 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/Issued/user-drawer.vue @@ -0,0 +1,213 @@ +<script setup lang="ts"> +import type { Role } from '#/api/system/user/model'; + +import { computed, h, ref } from 'vue'; + +import { useVbenDrawer } from '@vben/common-ui'; +import { $t } from '@vben/locales'; +import { cloneDeep } from '@vben/utils'; + +import { message, Tag } from 'ant-design-vue'; + +import { useVbenForm } from '#/adapter/form'; +import { findUserInfo, userAdd, userUpdate } from '#/api/system/user'; +import { authScopeOptions } from '#/views/system/role/data'; + +import { drawerSchema, UploadFileState } from './data'; +import { deptList } from '#/api/system/dept/index'; + +const emit = defineEmits<{ reload: [] }>(); + +const isUpdate = ref(false); //褰撳墠鏄惁涓虹紪杈� +const isLook = ref(false); //褰撳墠鏄惁涓烘煡鐪� +const title = computed(() => { + let text = ''; + if (isLook.value) { + text = $t('pages.common.look'); + } else if (isUpdate.value) { + text = $t('pages.common.edit'); + } else if (!isUpdate.value) { + text = $t('pages.common.add'); + } + return text; +}); + +const [BasicForm, formApi] = useVbenForm({ + commonConfig: { + formItemClass: 'col-span-2', + componentProps: { + class: 'w-full', + }, + labelWidth: 80, + }, + schema: drawerSchema(), + showDefaultActions: false, + wrapperClass: 'grid-cols-2', +}); + +/** + * 鐢熸垚瑙掕壊鐨勮嚜瀹氫箟label + * 涔熷彲浠ョ敤option鎻掓Ы鏉ュ仛 + * renderComponentContent: () => ({ + option: ({value, label, [disabled, key, title]}) => '', + }), + */ +function genRoleOptionlabel(role: Role) { + const found = authScopeOptions.find((item) => item.value === role.dataScope); + if (!found) { + return role.roleName; + } + return h('div', { class: 'flex items-center gap-[6px]' }, [ + h('span', null, role.roleName), + h(Tag, { color: found.color }, () => found.label), + ]); +} +// 鎵撳紑浠ュ強缂栬緫閮戒細璋冪敤璇ヤ簨浠� +const [BasicDrawer, drawerApi] = useVbenDrawer({ + onCancel: handleCancel, + onConfirm: handleConfirm, + async onOpenChange(isOpen) { + if (isOpen) { + // 鍒濆鍖栦竴涓嬭〃鍗曢�夐」 + const deptlist = await ( + await deptList() + ).map((item) => { + return { + ...item, + value: item.deptId, + label: item.deptName, + }; + }); + formApi.updateSchema([ + { + componentProps: { options: deptlist }, + fieldName: 'responsibleDepartment', + }, + ]); + } + + if (!isOpen) { + // 闇�瑕侀噸缃矖浣嶉�夋嫨 + formApi.updateSchema([ + { + componentProps: { options: [], placeholder: '璇峰厛閫夋嫨閮ㄩ棬' }, + fieldName: 'postIds', + }, + ]); + return null; + } + drawerApi.drawerLoading(true); + const { id } = drawerApi.getData() as { id?: number | string }; + const { look } = drawerApi.getData() as { look?: boolean }; + isUpdate.value = !!id; + isLook.value = !!look; + /** update鏃� 绂佺敤鐢ㄦ埛鍚嶄慨鏀� 涓嶆樉绀哄瘑鐮佹 */ + // 濡傛灉涓烘煡鐪嬪垯鎵�鏈夐」閮戒笉鍙紪杈� + formApi.updateSchema([ + { componentProps: { disabled: isLook.value }, fieldName: 'workName' }, + { componentProps: { disabled: isLook.value }, fieldName: 'workClass' }, + { componentProps: { disabled: isLook.value }, fieldName: 'workContent' }, + { + componentProps: { disabled: isLook.value }, + fieldName: 'projectBudget', + }, + { + componentProps: { disabled: isLook.value }, + fieldName: 'amountProject', + }, + { + componentProps: { disabled: isLook.value }, + fieldName: 'responsibleDepartment', + }, + { componentProps: { disabled: isLook.value }, fieldName: 'Head' }, + { componentProps: { disabled: isLook.value }, fieldName: 'Annual' }, + { + componentProps: { disabled: isLook.value }, + fieldName: 'assessmentTime', + }, + { + componentProps: { disabled: isLook.value }, + fieldName: 'assessmentIndicators', + }, + { componentProps: { disabled: isLook.value }, fieldName: 'File' }, + ]); + + drawerApi.setState({ + showConfirmButton: !isLook.value, + }); + + // 鏇存柊 && 璧嬪�� + // const { postIds, posts, roleIds, roles, user } = await findUserInfo(id); //璋冪敤鎺ュ彛鑾峰彇璇︾粏淇℃伅 + + const data = { + id: '0', //宸ヤ綔id锛岀紪鍙� + workName: '宸ヤ綔鍚嶇О1', //宸ヤ綔鍚嶇О + workClass: '宸ヤ綔绫诲埆', //宸ヤ綔绫诲埆 + workContent: '宸ヤ綔鍐呭', //宸ヤ綔鍐呭 + projectBudget: '椤圭洰棰勭畻', //椤圭洰棰勭畻 + amountProject: '椤圭洰閲戦', //椤圭洰閲戦 + responsibleDepartment: '璐熻矗閮ㄩ棬', //璐熻矗閮ㄩ棬 + Head: '璐熻矗浜�', //璐熻矗浜� + Annual: '2025', //骞村害 + assessmentTime: '2025-06-25', //鑰冩牳鏃堕棿 + assessmentIndicators: '鑰冩牳鎸囨爣', //鑰冩牳鎸囨爣 + File: '鏂囦欢鍦板潃', //鏂囦欢涓婁紶 + taskStatus: '浠诲姟鐘舵��', //浠诲姟鐘舵�� + assignmentStatus: '鍒嗛厤鐘舵��', //鍒嗛厤鐘舵�� + workProgress: '宸ヤ綔杩涘害', //宸ヤ綔杩涘害 + }; + + if (data && id) { + await Promise.all([ + // 娣诲姞鍩虹淇℃伅 + formApi.setValues(data), + ]); + } + drawerApi.drawerLoading(false); + console.log(isLook.value); + }, +}); +// 琛ㄥ崟鎻愪氦锛岀紪杈戜笌鏂板閮戒細璋冪敤杩欎釜鏂规硶浣� +async function handleConfirm() { + if( UploadFileState.value ){ + message.warn("褰撳墠鏈夋枃浠舵鍦ㄤ笂浼狅紝璇疯�愬績绛夊緟") + return + } + try { + drawerApi.drawerLoading(true); + const { valid } = await formApi.validate(); + if (!valid) { + return; + } + const data = cloneDeep(await formApi.getValues()); //琛ㄥ崟鍐呯殑鏁版嵁 + console.log(data) + // await (isUpdate.value ? userUpdate(data) : userAdd(data)); + emit('reload'); + await handleCancel(); + } catch (error) { + console.error(error); + } finally { + drawerApi.drawerLoading(false); + } +} + +async function handleCancel() { + drawerApi.close(); + await formApi.resetForm(); +} + +function IsShowConfirmButton(params: type) { + if (isLook.value) { + return false; + } + return true; +} + +// 鍦ㄨ繖鍔犺浇浜涙暟鎹� +</script> + +<template> + <BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]"> + <BasicForm /> + </BasicDrawer> +</template> diff --git a/ruoyi-ui/apps/web-antd/src/views/work/Issued/user-import-modal.vue b/ruoyi-ui/apps/web-antd/src/views/work/Issued/user-import-modal.vue new file mode 100644 index 0000000..fe81465 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/Issued/user-import-modal.vue @@ -0,0 +1,108 @@ +<script setup lang="ts"> +import type { UploadFile } from 'ant-design-vue/es/upload/interface'; + +import { h, ref, unref } from 'vue'; + +import { useVbenModal } from '@vben/common-ui'; +import { ExcelIcon, InBoxIcon } from '@vben/icons'; + +import { Modal, Switch, Upload } from 'ant-design-vue'; + +import { downloadImportTemplate, userImportData } from '#/api/system/user'; +import { commonDownloadExcel } from '#/utils/file/download'; + +const emit = defineEmits<{ reload: [] }>(); + +const UploadDragger = Upload.Dragger; + +const [BasicModal, modalApi] = useVbenModal({ + onCancel: handleCancel, + onConfirm: handleSubmit, +}); + +const fileList = ref<UploadFile[]>([]); +const checked = ref(false); + +async function handleSubmit() { + try { + modalApi.modalLoading(true); + if (fileList.value.length !== 1) { + handleCancel(); + return; + } + const data = { + file: fileList.value[0]!.originFileObj as Blob, + updateSupport: unref(checked), + }; + const { code, msg } = await userImportData(data); + let modal = Modal.success; + if (code === 200) { + emit('reload'); + } else { + modal = Modal.error; + } + handleCancel(); + modal({ + content: h('div', { + class: 'max-h-[260px] overflow-y-auto', + innerHTML: msg, // 鍚庡彴宸茬粡澶勭悊xss闂 + }), + title: '鎻愮ず', + }); + } catch (error) { + console.warn(error); + modalApi.close(); + } finally { + modalApi.modalLoading(false); + } +} + +function handleCancel() { + modalApi.close(); + fileList.value = []; + checked.value = false; +} +</script> + +<template> + <BasicModal + :close-on-click-modal="false" + :fullscreen-button="false" + title="鐢ㄦ埛瀵煎叆" + > + <!-- z-index涓嶈缃細閬尅妯℃澘涓嬭浇loading --> + <!-- 鎵嬪姩澶勭悊 鑰屼笉鏄斁鍏ユ枃浠跺氨涓婁紶 --> + <UploadDragger + v-model:file-list="fileList" + :before-upload="() => false" + :max-count="1" + :show-upload-list="true" + accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" + > + <p class="ant-upload-drag-icon flex items-center justify-center"> + <InBoxIcon class="text-primary size-[48px]" /> + </p> + <p class="ant-upload-text">鐐瑰嚮鎴栬�呮嫋鎷藉埌姝ゅ涓婁紶鏂囦欢</p> + </UploadDragger> + <div class="mt-2 flex flex-col gap-2"> + <div class="flex items-center gap-2"> + <span>鍏佽瀵煎叆xlsx, xls鏂囦欢</span> + <a-button + type="link" + @click="commonDownloadExcel(downloadImportTemplate, '鐢ㄦ埛瀵煎叆妯℃澘')" + > + <div class="flex items-center gap-[4px]"> + <ExcelIcon /> + <span>涓嬭浇妯℃澘</span> + </div> + </a-button> + </div> + <div class="flex items-center gap-2"> + <span :class="{ 'text-red-500': checked }"> + 鏄惁鏇存柊/瑕嗙洊宸插瓨鍦ㄧ殑鐢ㄦ埛鏁版嵁 + </span> + <Switch v-model:checked="checked" /> + </div> + </div> + </BasicModal> +</template> diff --git a/ruoyi-ui/apps/web-antd/src/views/work/Issued/user-info-modal.vue b/ruoyi-ui/apps/web-antd/src/views/work/Issued/user-info-modal.vue new file mode 100644 index 0000000..63559af --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/Issued/user-info-modal.vue @@ -0,0 +1,62 @@ +<script setup lang="ts"> +import type { User } from '#/api/system/user/model'; + +import { useVbenModal } from '@vben/common-ui'; + +import { findUserInfo } from '#/api/system/user'; +import { Description, useDescription } from '#/components/description'; + +import { descSchema } from './info'; + +const [BasicModal, modalApi] = useVbenModal({ + onOpenChange: handleOpenChange, +}); + +const [registerDescription, { setDescProps }] = useDescription({ + column: 1, + labelStyle: { + minWidth: '150px', + width: '150px', + }, + schema: descSchema, +}); + +async function handleOpenChange(open: boolean) { + if (!open) { + return null; + } + modalApi.modalLoading(true); + + const { userId } = modalApi.getData() as { userId: number | string }; + const response = await findUserInfo(userId); + // 澶栭儴鐨剅oleIds postIds鎵嶆槸鐪熸瀵瑰簲鐨� 鏂板鏃朵负绌� + // posts鏈変负Null鐨勬儏鍐� 闇�瑕佺粰榛樿鍊� + const { postIds = [], posts = [], roleIds = [], roles = [], user } = response; + + const postNames = posts + .filter((item) => postIds.includes(item.postId)) + .map((item) => item.postName); + + const roleNames = roles + .filter((item) => roleIds.includes(item.roleId)) + .map((item) => item.roleName); + + interface UserWithNames extends User { + postNames: string[]; + roleNames: string[]; + } + (user as UserWithNames).postNames = postNames; + (user as UserWithNames).roleNames = roleNames; + + // 璧嬪�� + setDescProps({ data: user }); + + modalApi.modalLoading(false); +} +</script> + +<template> + <BasicModal :footer="false" :fullscreen-button="false" title="鐢ㄦ埛淇℃伅"> + <Description @register="registerDescription" /> + </BasicModal> +</template> diff --git a/ruoyi-ui/apps/web-antd/src/views/work/Issued/user-reset-pwd-modal.vue b/ruoyi-ui/apps/web-antd/src/views/work/Issued/user-reset-pwd-modal.vue new file mode 100644 index 0000000..a3acf33 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/Issued/user-reset-pwd-modal.vue @@ -0,0 +1,111 @@ +<script setup lang="ts"> +import type { ResetPwdParam, User } from '#/api/system/user/model'; + +import { useVbenModal, z } from '@vben/common-ui'; + +import { useVbenForm } from '#/adapter/form'; +import { userResetPassword } from '#/api/system/user'; +import { Description, useDescription } from '#/components/description'; + +const emit = defineEmits<{ reload: [] }>(); + +const [BasicModal, modalApi] = useVbenModal({ + onCancel: handleCancel, + onConfirm: handleSubmit, + onOpenChange: handleOpenChange, +}); + +const [registerDescription, { setDescProps }] = useDescription({ + column: 1, + schema: [ + { + field: 'userId', + label: '鐢ㄦ埛ID', + }, + { + field: 'userName', + label: '鐢ㄦ埛鍚�', + }, + { + field: 'nickName', + label: '鏄电О', + }, + ], +}); + +const [BasicForm, formApi] = useVbenForm({ + schema: [ + { + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + fieldName: 'userId', + label: '鐢ㄦ埛ID', + rules: 'required', + }, + { + component: 'InputPassword', + componentProps: { + placeholder: '璇疯緭鍏ユ柊鐨勫瘑鐮�, 瀵嗙爜闀垮害涓�5 - 20', + }, + fieldName: 'password', + label: '鏂扮殑瀵嗙爜', + rules: z + .string() + .min(5, { message: '瀵嗙爜闀垮害涓�5 - 20' }) + .max(20, { message: '瀵嗙爜闀垮害涓�5 - 20' }), + }, + ], + showDefaultActions: false, + commonConfig: { + labelWidth: 80, + }, +}); + +async function handleOpenChange(open: boolean) { + if (!open) { + return null; + } + const { record } = modalApi.getData() as { record: User }; + setDescProps({ data: record }, true); + await formApi.setValues({ userId: record.userId }); +} + +async function handleSubmit() { + try { + modalApi.modalLoading(true); + const { valid } = await formApi.validate(); + if (!valid) { + return; + } + const data = await formApi.getValues(); + await userResetPassword(data as ResetPwdParam); + emit('reload'); + handleCancel(); + } catch (error) { + console.error(error); + } finally { + modalApi.modalLoading(false); + } +} + +async function handleCancel() { + modalApi.close(); + await formApi.resetForm(); +} +</script> + +<template> + <BasicModal + :close-on-click-modal="false" + :fullscreen-button="false" + title="閲嶇疆瀵嗙爜" + > + <div class="flex flex-col gap-[12px]"> + <Description @register="registerDescription" /> + <BasicForm /> + </div> + </BasicModal> +</template> diff --git a/ruoyi-ui/apps/web-antd/src/views/work/myWork/data.tsx b/ruoyi-ui/apps/web-antd/src/views/work/myWork/data.tsx new file mode 100644 index 0000000..b0b9fd5 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/myWork/data.tsx @@ -0,0 +1,262 @@ +import type { FormSchemaGetter } from '#/adapter/form'; +import type { VxeGridProps } from '#/adapter/vxe-table'; + +import { DictEnum } from '@vben/constants'; +import { getPopupContainer } from '@vben/utils'; +import { useAppConfig } from '@vben/hooks'; +import { useAccessStore } from '@vben/stores'; +import type { UploadFile } from 'ant-design-vue'; + +import { z } from '#/adapter/form'; +import { getDictOptions } from '#/utils/dict'; +import { Item } from 'ant-design-vue/es/menu'; +// import { OptionsItem } from "" + +export const querySchema: FormSchemaGetter = () => [ + { + component: 'Input', + fieldName: 'workName', + label: '宸ヤ綔鍚嶇О', + }, + { + component: 'Select', + componentProps: { + getPopupContainer, + options: getDictOptions(DictEnum.TASK_STATUS), + }, + fieldName: 'workClass', + label: '宸ヤ綔绫诲埆', + }, + { + component: 'Select', + componentProps: { + getPopupContainer, + options: getDictOptions(DictEnum.TASK_STATUS), + }, + fieldName: 'taskStatus', + label: '浠诲姟鐘舵��', + }, + { + component: 'Input', + fieldName: 'Annual', + label: '骞村害', + }, +]; + +export const columns: VxeGridProps['columns'] = [ + { type: 'checkbox', width: 60 }, + { + field: 'workName', + title: '鍚嶇О', + }, + { + field: 'id', + title: '缂栧彿', + }, + { + field: 'workClass', + title: '绫诲埆', + }, + { + field: 'assessmentTime', + title: '鑰冩牳鏃堕棿', + }, + { + field: 'taskStatus', + title: '浠诲姟鐘舵��', + }, + { + field: 'Annual', + title: '骞村害', + }, + { + field: 'workProgress', + title: '宸ヤ綔杩涘害', + }, + { + field: 'action', + fixed: 'right', + slots: { default: 'action' }, + title: '鎿嶄綔', + width: 180, + }, +]; +// 宸ヤ綔璇︽儏 +export const drawerSchema: FormSchemaGetter = () => [ + { + component: 'Input', + fieldName: 'workName', + label: '宸ヤ綔鍚嶇О', + rules: 'required' + }, + { + component: 'Input', + fieldName: 'workClass', + label: '宸ヤ綔绫诲埆', + rules: 'required', + }, + { + component: 'Input', + fieldName: 'workContent', + label: '宸ヤ綔鍐呭' + }, + { + component: 'Input', + fieldName: 'projectBudget', + label: '椤圭洰棰勭畻%', + defaultValue: undefined + }, + { + component: 'Input', + fieldName: 'amountProject', + defaultValue: undefined, + label: '椤圭洰閲戦' + }, + { + component: 'Select', + componentProps: { + getPopupContainer, + options: [] + }, + fieldName: 'responsibleDepartment', + label: '璐熻矗閮ㄩ棬', + rules: 'required', + }, + { + component: 'Select', + componentProps: { + getPopupContainer, + options: getDictOptions(DictEnum.SYS_NORMAL_DISABLE) + }, + fieldName: 'Head', + label: '璐熻矗浜�', + }, + { + component: 'DatePicker', + componentProps: { + format: 'YYYY', + showTime: true, + valueFormat: 'YYYY', + picker: "year", + getPopupContainer, + }, + fieldName: 'Annual', + label: '骞村害', + rules: 'required', + }, + { + component: 'DatePicker', + componentProps: { + format: 'YYYY-MM-DD', + showTime: true, + valueFormat: 'YYYY-MM-DD', + getPopupContainer, + }, + fieldName: 'assessmentTime', + label: '鑰冩牳鏃堕棿', + rules: 'required', + }, + { + component: 'Select', + fieldName: 'assessmentIndicators', + formItemClass: 'items-baseline', + label: '鑰冩牳鎸囨爣', + }, + { + fieldName: 'File', + component: 'NewFileUpload', + componentProps: { + action: uploadUrl, + headers: headers, + maxSize: 20, + onDelete:(file: UploadFile)=>{ + console.log("瀛樺湪鏂囦欢琚垹闄�",file) + } + }, + formItemClass: 'items-baseline', + label: '鏂囦欢涓婁紶', + } +]; + +// 宸ヤ綔涓嬪彂 +export const AssigningWorkSchema: FormSchemaGetter = () => [ + { + component: 'Select', + fieldName: 'assessmentIndicators', + formItemClass: 'items-baseline', + label: '璐熻矗浜�', + }, +] + +// 宸ヤ綔姹囨姤 +export const ReportScheme: FormSchemaGetter = () => [ + { + component: 'Input', + fieldName: 'workName', + label: '宸ヤ綔鍚嶇О', + rules: 'required' + }, + { + component: 'Input', + fieldName: 'workClass', + label: '宸ヤ綔绫诲埆', + rules: 'required', + }, + { + component: 'Textarea', + fieldName: 'workContent', + label: '宸ヤ綔鍐呭' + }, + { + component: 'DatePicker', + componentProps: { + format: 'YYYY-MM-DD', + showTime: true, + valueFormat: 'YYYY-MM-DD', + getPopupContainer, + }, + fieldName: 'DebriefingTime', + label: '姹囨姤鏃堕棿', + rules: 'required', + }, + { + component: 'Input', + fieldName: 'workProgress', + label: '瀹屾垚杩涘害' + }, + { + fieldName: 'File', + component: 'NewFileUpload', + componentProps: { + action: uploadUrl, + headers: headers, + maxSize: 20, + onDelete:(file: UploadFile)=>{ + console.log("瀛樺湪鏂囦欢琚垹闄�",file) + } + }, + formItemClass: 'items-baseline', + label: '鏂囦欢涓婁紶', + }, + { + component: 'Input', + fieldName: 'workProgress', + label: '璧勯噾浣跨敤(涓囧厓)' + }, + { + component: 'Textarea', + fieldName: 'remark', + label: '澶囨敞' + }, +] + +const { apiURL, clientId } = useAppConfig( + import.meta.env, + import.meta.env.PROD, +); +const uploadUrl = `${apiURL}/knowledge/attach/upload`; +const accessStore = useAccessStore(); +const headers = { + Authorization: `Bearer ${accessStore.accessToken}`, + clientId, +}; diff --git a/ruoyi-ui/apps/web-antd/src/views/work/myWork/dept-tree.vue b/ruoyi-ui/apps/web-antd/src/views/work/myWork/dept-tree.vue new file mode 100644 index 0000000..df9d6c2 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/myWork/dept-tree.vue @@ -0,0 +1,128 @@ +<script setup lang="ts"> +import type { PropType } from 'vue'; + +import type { DeptTree } from '#/api/system/user/model'; + +import { onMounted, ref } from 'vue'; + +import { SyncOutlined } from '@ant-design/icons-vue'; +import { Empty, InputSearch, Skeleton, Tree } from 'ant-design-vue'; + +import { getDeptTree } from '#/api/system/user'; + +defineOptions({ inheritAttrs: false }); + +withDefaults(defineProps<{ showSearch?: boolean }>(), { showSearch: true }); + +const emit = defineEmits<{ + /** + * 鐐瑰嚮鍒锋柊鎸夐挳鐨勪簨浠� + */ + reload: []; + /** + * 鐐瑰嚮鑺傜偣鐨勪簨浠� + */ + select: []; +}>(); + +const selectDeptId = defineModel('selectDeptId', { + required: true, + type: Array as PropType<string[]>, +}); + +const searchValue = defineModel('searchValue', { + type: String, + default: '', +}); + +/** 閮ㄩ棬鏁版嵁婧� */ +type DeptTreeArray = DeptTree[]; +const deptTreeArray = ref<DeptTreeArray>([]); +/** 楠ㄦ灦灞忓姞杞� */ +const showTreeSkeleton = ref<boolean>(true); + +async function loadTree() { + showTreeSkeleton.value = true; + searchValue.value = ''; + selectDeptId.value = []; + + const ret = await getDeptTree(); + console.log( "ret", ret ) + deptTreeArray.value = ret; + showTreeSkeleton.value = false; +} + +async function handleReload() { + await loadTree(); + emit('reload'); +} + +onMounted(loadTree); +</script> + +<template> + <div :class="$attrs.class"> + <Skeleton + :loading="showTreeSkeleton" + :paragraph="{ rows: 8 }" + active + class="p-[8px]" + > + <div + class="bg-background flex h-full flex-col overflow-y-auto rounded-lg" + > + <!-- 鍥哄畾鍦ㄩ《閮� 蹇呴』鍔犱笂bg-background鑳屾櫙鑹� 鍚﹀垯浼氫骇鐢�'绌块��'鏁堟灉 --> + <div + v-if="showSearch" + class="bg-background z-100 sticky left-0 top-0 p-[8px]" + > + <InputSearch + v-model:value="searchValue" + :placeholder="$t('pages.common.search')" + size="small" + > + <template #enterButton> + <a-button @click="handleReload"> + <SyncOutlined class="text-primary" /> + </a-button> + </template> + </InputSearch> + </div> + <div class="h-full overflow-x-hidden px-[8px]"> + <Tree + v-bind="$attrs" + v-if="deptTreeArray.length > 0" + v-model:selected-keys="selectDeptId" + :class="$attrs.class" + :field-names="{ title: 'label', key: 'id' }" + :show-line="{ showLeafIcon: false }" + :tree-data="deptTreeArray" + :virtual="false" + default-expand-all + @select="$emit('select')" + > + <template #title="{ label }"> + <span v-if="label.indexOf(searchValue) > -1"> + {{ label.substring(0, label.indexOf(searchValue)) }} + <span style="color: #f50">{{ searchValue }}</span> + {{ + label.substring( + label.indexOf(searchValue) + searchValue.length, + ) + }} + </span> + <span v-else>{{ label }}</span> + </template> + </Tree> + <!-- 浠呮湰浜烘暟鎹潈闄� 鍙互鑰冭檻鐩存帴涓嶆樉绀� --> + <div v-else class="mt-5"> + <Empty + :image="Empty.PRESENTED_IMAGE_SIMPLE" + description="鏃犻儴闂ㄦ暟鎹�" + /> + </div> + </div> + </div> + </Skeleton> + </div> +</template> diff --git a/ruoyi-ui/apps/web-antd/src/views/work/myWork/index.vue b/ruoyi-ui/apps/web-antd/src/views/work/myWork/index.vue index 7dabebf..149cfd5 100644 --- a/ruoyi-ui/apps/web-antd/src/views/work/myWork/index.vue +++ b/ruoyi-ui/apps/web-antd/src/views/work/myWork/index.vue @@ -1,13 +1,298 @@ -<template> - <div>鎴戠殑宸ヤ綔</div> -</template> +<!-- 鎴戠殑宸ヤ綔 --> +<script setup lang="ts"> +import type { VbenFormProps } from '@vben/common-ui'; -<script> -export default { - name: "index" +import type { VxeGridProps } from '#/adapter/vxe-table'; +import type { User } from '#/api/system/user/model'; + +import { ref } from 'vue'; + +import { useAccess } from '@vben/access'; +import { Page, useVbenDrawer, useVbenModal } from '@vben/common-ui'; +import { $t } from '@vben/locales'; +import { preferences } from '@vben/preferences'; +import { getVxePopupContainer } from '@vben/utils'; + +import { + Avatar, + Dropdown, + Menu, + MenuItem, + Modal, + Popconfirm, + Space, +} from 'ant-design-vue'; + +import { useVbenVxeGrid, vxeCheckboxChecked } from '#/adapter/vxe-table'; +import { + userExport, + userList, + userRemove, + userStatusChange, +} from '#/api/system/user'; +import { TableSwitch } from '#/components/table'; +import { commonDownloadExcel } from '#/utils/file/download'; + +import { columns, querySchema } from './data'; +import userDrawer from './user-drawer.vue'; //宸ヤ綔璇︽儏 +import userAssignWork from './user-assign-work.vue'; //宸ヤ綔涓嬪彂 +import userReport from './user-report.vue'; //宸ヤ綔姹囨姤 +import userImportModal from './user-import-modal.vue'; +import userInfoModal from './user-info-modal.vue'; +import userResetPwdModal from './user-reset-pwd-modal.vue'; + +/** + * 瀵煎叆 + */ +const [UserImpotModal, userImportModalApi] = useVbenModal({ + connectedComponent: userImportModal, +}); + +function handleImport() { + userImportModalApi.open(); } + +// 宸﹁竟閮ㄩ棬鐢� +const selectDeptId = ref<string[]>([]); + +const formOptions: VbenFormProps = { + schema: querySchema(), + commonConfig: { + labelWidth: 80, + componentProps: { + allowClear: true, + }, + }, + wrapperClass: 'grid-cols-1 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4', + handleReset: async () => { + selectDeptId.value = []; + + const { formApi, reload } = tableApi; + await formApi.resetForm(); + const formValues = formApi.form.values; + formApi.setLatestSubmissionValues(formValues); + await reload(formValues); + }, + // 鏃ユ湡閫夋嫨鏍煎紡鍖� + fieldMappingTime: [ + [ + 'createTime', + ['params[beginTime]', 'params[endTime]'], + ['YYYY-MM-DD 00:00:00', 'YYYY-MM-DD 23:59:59'], + ], + ], +}; + +const gridOptions: VxeGridProps = { + checkboxConfig: { + // 楂樹寒 + highlight: true, + // 缈婚〉鏃朵繚鐣欓�変腑鐘舵�� + reserve: true, + // 鐐瑰嚮琛岄�変腑 + trigger: 'default', + checkMethod: ({ row }) => row?.userId !== 1, + }, + columns, + height: 'auto', + keepSource: true, + pagerConfig: {}, + proxyConfig: { + ajax: { + // 鑾峰彇椤甸潰鏁版嵁锛屾悳绱紝閲嶇疆涔熸槸 + /* + @params page:椤电爜鍙傛暟 + @params formValues:琛ㄥ崟鍙傛暟 + */ + query: async ({ page }, formValues = {}) => { + console.log("鑾峰彇椤甸潰鏁版嵁锛�") + const res = { + rows:[ { + id:"0", //宸ヤ綔id锛岀紪鍙� + workName:"宸ヤ綔鍚嶇О1", //宸ヤ綔鍚嶇О + workClass:"宸ヤ綔绫诲埆", //宸ヤ綔绫诲埆 + workContent:"宸ヤ綔鍐呭", //宸ヤ綔鍐呭 + projectBudget:"椤圭洰棰勭畻", //椤圭洰棰勭畻 + amountProject:"椤圭洰閲戦", //椤圭洰閲戦 + responsibleDepartment:"璐熻矗閮ㄩ棬", //璐熻矗閮ㄩ棬 + Head:"璐熻矗浜�", //璐熻矗浜� + Annual:"骞村害", //骞村害 + assessmentTime:"鑰冩牳鏃堕棿",//鑰冩牳鏃堕棿 + assessmentIndicators:"鑰冩牳鎸囨爣", //鑰冩牳鎸囨爣 + File:"鏂囦欢鍦板潃", //鏂囦欢涓婁紶 + taskStatus:"浠诲姟鐘舵��", //浠诲姟鐘舵�� + assignmentStatus:"鍒嗛厤鐘舵��", //鍒嗛厤鐘舵�� + workProgress:"宸ヤ綔杩涘害" //宸ヤ綔杩涘害 + } ], + total:1 + } + console.log( "res", res) + return res + }, + }, + }, + rowConfig: { + keyField: 'userId', + height: 48, + }, + id: 'system-user-index', +}; +const [BasicTable, tableApi] = useVbenVxeGrid({ + formOptions, + gridOptions, +}); +// 鏌ョ湅璇︽儏 +const [UserDrawer, userDrawerApi] = useVbenDrawer({ + connectedComponent: userDrawer, +}); +// 宸ヤ綔涓嬪彂 +const [userAssignWorkDrawer, userAssignWorkApi] = useVbenDrawer({ + connectedComponent: userAssignWork +}) +// 宸ヤ綔姹囨姤 +const [userReportDrawer, userReportApi] = useVbenDrawer({ + connectedComponent: userReport +}) +// 姝ゆ柟娉曟殏鏈惎鐢� +function handleAdd() { + userDrawerApi.setData({}); + userDrawerApi.open(); +} +// 鏌ョ湅璇︽儏 +function handleLook(row: any) { + userDrawerApi.setData({ id: row.id, look: true }); + userDrawerApi.open(); +} +// 宸ヤ綔涓嬪彂 +function handleAssigningWork(row: any) { + userAssignWorkApi.setData({id:row.id, look:true}) + userAssignWorkApi.open() +} +// 宸ヤ綔姹囨姤 +function handleReport(row: any) { + userReportApi.setData({ id: row.id, row: row }); + userReportApi.open(); +} +function handleMultiDelete() { + const rows = tableApi.grid.getCheckboxRecords(); + const ids = rows.map((row: User) => row.userId); + Modal.confirm({ + title: '鎻愮ず', + okType: 'danger', + content: `纭鍒犻櫎閫変腑鐨�${ids.length}鏉¤褰曞悧锛焋, + onOk: async () => { + await userRemove(ids); + await tableApi.query(); + }, + }); +} + +function handleDownloadExcel() { + commonDownloadExcel(userExport, '鐢ㄦ埛绠$悊', tableApi.formApi.form.values, { + fieldMappingTime: formOptions.fieldMappingTime, + }); +} + +const [UserInfoModal, userInfoModalApi] = useVbenModal({ + connectedComponent: userInfoModal, +}); +function handleUserInfo(row: User) { + userInfoModalApi.setData({ userId: row.userId }); + userInfoModalApi.open(); +} + +const [UserResetPwdModal, userResetPwdModalApi] = useVbenModal({ + connectedComponent: userResetPwdModal, +}); + +function handleResetPwd(record: User) { + userResetPwdModalApi.setData({ record }); + userResetPwdModalApi.open(); +} + +const { hasAccessByCodes } = useAccess(); </script> -<style scoped> - -</style> +<template> + <Page :auto-content-height="true"> + <div class="flex h-full gap-[8px]"> + <BasicTable class="flex-1 overflow-hidden" table-title="宸ヤ綔鍒楄〃"> + <template #toolbar-tools> + <Space> + <!-- 瀵煎嚭 --> + <!-- <a-button + @click="handleDownloadExcel" + > + {{ $t('pages.common.export') }} + </a-button> --> + <!-- 瀵煎叆 --> + <!-- <a-button + v-access:code="['system:user:import']" + @click="handleImport" + > + {{ $t('pages.common.import') }} + </a-button> --> + <!-- 鍒犻櫎 --> + <a-button + :disabled="!vxeCheckboxChecked(tableApi)" + danger + type="primary" + @click="handleMultiDelete" + > + {{ $t('pages.common.delete') }} + </a-button> + <!-- 鏂板 --> + <!-- <a-button + type="primary" + @click="handleAdd" + > + {{ $t('pages.common.WorkIssued') }} + </a-button> --> + </Space> + </template> + <template #avatar="{ row }"> + <!-- 鍙兘瑕佸垽鏂┖瀛楃涓叉儏鍐� 鎵�浠ユ病鏈変娇鐢�?? --> + <Avatar :src="row.avatar || preferences.app.defaultAvatar" /> + </template> + <template #status="{ row }"> + <TableSwitch + v-model="row.status" + :api="() => userStatusChange(row)" + :disabled=" + row.userId === 1 || !hasAccessByCodes(['system:user:edit']) + " + :reload="() => tableApi.query()" + /> + </template> + <template #action="{ row }"> + <Space> + <!-- 鏌ョ湅 --> + <ghost-button + @click.stop="handleLook(row)" + > + {{ $t('pages.common.look') }} + </ghost-button> + <!-- 姹囨姤 --> + <ghost-button + @click.stop="handleReport(row)" + > + {{ $t('pages.common.report') }} + </ghost-button> + <!-- 宸ヤ綔涓嬪彂 --> + <ghost-button + @click.stop="handleAssigningWork(row)" + > + {{ $t('pages.common.AssigningWork') }} + </ghost-button> + </Space> + </template> + </BasicTable> + </div> + <UserImpotModal @reload="tableApi.query()" /> + <UserDrawer @reload="tableApi.query()" /> <!-- 宸ヤ綔璇︽儏 --> + <userAssignWorkDrawer @reload="tableApi.query()" /> <!-- 宸ヤ綔涓嬪彂 --> + <userReportDrawer @reload="tableApi.query()" /> <!-- 宸ヤ綔姹囨姤 --> + <UserInfoModal /> + <UserResetPwdModal /> + </Page> +</template> diff --git a/ruoyi-ui/apps/web-antd/src/views/work/myWork/info.tsx b/ruoyi-ui/apps/web-antd/src/views/work/myWork/info.tsx new file mode 100644 index 0000000..cd00bb8 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/myWork/info.tsx @@ -0,0 +1,129 @@ +import type { DescItem } from '#/components/description'; + +import { DictEnum } from '@vben/constants'; + +import { Tag } from 'ant-design-vue'; +import dayjs from 'dayjs'; +import duration from 'dayjs/plugin/duration'; +import relativeTime from 'dayjs/plugin/relativeTime'; + +import { renderDict } from '#/utils/render'; + +dayjs.extend(duration); +dayjs.extend(relativeTime); + +function renderTags(list: string[]) { + return ( + <div class="flex flex-row flex-wrap gap-0.5"> + {list.map((item) => ( + <Tag key={item}>{item}</Tag> + ))} + </div> + ); +} + +export const descSchema: DescItem[] = [ + { + field: 'userId', + label: '鐢ㄦ埛ID', + }, + { + field: 'status', + label: '鐢ㄦ埛鐘舵��', + render(value) { + return renderDict(value, DictEnum.SYS_NORMAL_DISABLE); + }, + }, + { + field: 'nickName', + label: '鐢ㄦ埛淇℃伅', + render(_, data) { + const { deptName = '鏆傛棤閮ㄩ棬淇℃伅', nickName, userName } = data; + // 涓轰簡鍏煎鏂扮増鏈拰鏃х増鏈� + let currentDept = deptName; + if (data.dept && data.dept.deptName) { + currentDept = data.dept.deptName; + } + return `${userName} / ${nickName} / ${currentDept}`; + }, + }, + { + field: 'phonenumber', + label: '鎵嬫満鍙�', + render(value) { + return value || '鏈缃墜鏈哄彿鐮�'; + }, + }, + { + field: 'email', + label: '閭', + render(value) { + return value || '鏈缃偖绠卞湴鍧�'; + }, + }, + { + field: 'postNames', + label: '宀椾綅', + render(value) { + if (Array.isArray(value) && value.length === 0) { + return '鏆傛棤淇℃伅'; + } + return renderTags(value); + }, + }, + { + field: 'roleNames', + label: '鏉冮檺', + render(value) { + if (Array.isArray(value) && value.length === 0) { + return '鏆傛棤淇℃伅'; + } + return renderTags(value); + }, + }, + { + field: 'createTime', + label: '鍒涘缓鏃堕棿', + }, + { + field: 'loginIp', + label: '涓婃鐧诲綍IP', + render(value) { + return value || <span class="text-orange-500">浠庢湭鐧诲綍杩�</span>; + }, + }, + { + field: 'loginDate', + label: '涓婃鐧诲綍鏃堕棿', + render(value) { + if (!value) { + return <span class="text-orange-500">浠庢湭鐧诲綍杩�</span>; + } + // 榛樿en鏄剧ず + dayjs.locale('zh-cn'); + // 璁$畻鐩稿樊绉掓暟 + const diffSeconds = dayjs().diff(dayjs(value), 'second'); + /** + * 杞负鏃堕棿鏄剧ず(x鏈� x澶�) + * https://dayjs.fenxianglu.cn/category/duration.html#%E4%BA%BA%E6%80%A7%E5%8C%96 + * + */ + const diffText = dayjs.duration(diffSeconds, 'seconds').humanize(); + return ( + <div class="flex gap-2"> + {value} + <Tag bordered={false} color="cyan"> + {diffText}鍓� + </Tag> + </div> + ); + }, + }, + { + field: 'remark', + label: '澶囨敞', + render(value) { + return value || '鏃�'; + }, + }, +]; diff --git a/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-assign-work.vue b/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-assign-work.vue new file mode 100644 index 0000000..a394a80 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-assign-work.vue @@ -0,0 +1,170 @@ +<script setup lang="ts"> +import type { Role } from '#/api/system/user/model'; + +import { computed, h, ref } from 'vue'; + +import { useVbenDrawer } from '@vben/common-ui'; +import { $t } from '@vben/locales'; +import { cloneDeep } from '@vben/utils'; + +import { Tag } from 'ant-design-vue'; + +import { useVbenForm } from '#/adapter/form'; +import { findUserInfo, userAdd, userUpdate } from '#/api/system/user'; +import { authScopeOptions } from '#/views/system/role/data'; + +import { drawerSchema, AssigningWorkSchema } from './data'; +import { deptList } from '#/api/system/dept/index'; + +const emit = defineEmits<{ reload: [] }>(); + +const isUpdate = ref(false); //褰撳墠鏄惁涓虹紪杈� +const isLook = ref(false); //褰撳墠鏄惁涓烘煡鐪� +const title = computed(() => { + let text = ''; + text = "宸ヤ綔涓嬪彂" + return text; +}); + +const [BasicForm, formApi] = useVbenForm({ + commonConfig: { + formItemClass: 'col-span-2', + componentProps: { + class: 'w-full', + }, + labelWidth: 80, + }, + schema: AssigningWorkSchema(), + showDefaultActions: false, + wrapperClass: 'grid-cols-2', +}); + +/** + * 鐢熸垚瑙掕壊鐨勮嚜瀹氫箟label + * 涔熷彲浠ョ敤option鎻掓Ы鏉ュ仛 + * renderComponentContent: () => ({ + option: ({value, label, [disabled, key, title]}) => '', + }), + */ +function genRoleOptionlabel(role: Role) { + const found = authScopeOptions.find((item) => item.value === role.dataScope); + if (!found) { + return role.roleName; + } + return h('div', { class: 'flex items-center gap-[6px]' }, [ + h('span', null, role.roleName), + h(Tag, { color: found.color }, () => found.label), + ]); +} +// 鎵撳紑浠ュ強缂栬緫閮戒細璋冪敤璇ヤ簨浠� +const [BasicDrawer, drawerApi] = useVbenDrawer({ + onCancel: handleCancel, + onConfirm: handleConfirm, + async onOpenChange(isOpen) { + drawerApi.drawerLoading(true); + if (isOpen) { + // 鍒濆鍖栦竴涓嬭〃鍗曢�夐」 + const deptlist = await ( + await deptList() + ).map((item) => { + return { + ...item, + value: item.deptId, + label: item.deptName, + }; + }); + formApi.updateSchema([ + { + componentProps: { options: deptlist }, + fieldName: 'responsibleDepartment', + }, + ]); + } + + if (!isOpen) { + // 闇�瑕侀噸缃矖浣嶉�夋嫨 + formApi.updateSchema([ + { + componentProps: { options: [], placeholder: '璇峰厛閫夋嫨閮ㄩ棬' }, + fieldName: 'postIds', + }, + ]); + return null; + } + const { id } = drawerApi.getData() as { id?: number | string }; + isUpdate.value = !!id; + + // 鏇存柊 && 璧嬪�� + // const { postIds, posts, roleIds, roles, user } = await findUserInfo(id); //璋冪敤鎺ュ彛鑾峰彇璇︾粏淇℃伅 + + const data = { + id: '0', //宸ヤ綔id锛岀紪鍙� + workName: '宸ヤ綔鍚嶇О1', //宸ヤ綔鍚嶇О + workClass: '宸ヤ綔绫诲埆', //宸ヤ綔绫诲埆 + workContent: '宸ヤ綔鍐呭', //宸ヤ綔鍐呭 + projectBudget: '椤圭洰棰勭畻', //椤圭洰棰勭畻 + amountProject: '椤圭洰閲戦', //椤圭洰閲戦 + responsibleDepartment: '璐熻矗閮ㄩ棬', //璐熻矗閮ㄩ棬 + Head: '璐熻矗浜�', //璐熻矗浜� + Annual: '2025', //骞村害 + assessmentTime: '2025-06-25', //鑰冩牳鏃堕棿 + assessmentIndicators: '鑰冩牳鎸囨爣', //鑰冩牳鎸囨爣 + File: '鏂囦欢鍦板潃', //鏂囦欢涓婁紶 + taskStatus: '浠诲姟鐘舵��', //浠诲姟鐘舵�� + assignmentStatus: '鍒嗛厤鐘舵��', //鍒嗛厤鐘舵�� + workProgress: '宸ヤ綔杩涘害', //宸ヤ綔杩涘害 + }; + + console.log(data); + console.log(id); + + if (data && id) { + await Promise.all([ + // 娣诲姞鍩虹淇℃伅 + formApi.setValues(data), + ]); + } + drawerApi.drawerLoading(false); + console.log(isLook.value); + }, +}); +// 琛ㄥ崟鎻愪氦锛岀紪杈戜笌鏂板閮戒細璋冪敤杩欎釜鏂规硶浣� +async function handleConfirm() { + try { + drawerApi.drawerLoading(true); + const { valid } = await formApi.validate(); + if (!valid) { + return; + } + const data = cloneDeep(await formApi.getValues()); //琛ㄥ崟鍐呯殑鏁版嵁 + console.log(data) + // await (isUpdate.value ? userUpdate(data) : userAdd(data)); + emit('reload'); + await handleCancel(); + } catch (error) { + console.error(error); + } finally { + drawerApi.drawerLoading(false); + } +} + +async function handleCancel() { + drawerApi.close(); + await formApi.resetForm(); +} + +function IsShowConfirmButton(params: type) { + if (isLook.value) { + return false; + } + return true; +} + +// 鍦ㄨ繖鍔犺浇浜涙暟鎹� +</script> + +<template> + <BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]"> + <BasicForm /> + </BasicDrawer> +</template> diff --git a/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-drawer.vue b/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-drawer.vue new file mode 100644 index 0000000..94d90a6 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-drawer.vue @@ -0,0 +1,218 @@ +<script setup lang="ts"> +import type { Role } from '#/api/system/user/model'; + +import { computed, h, ref } from 'vue'; + +import { useVbenDrawer } from '@vben/common-ui'; +import { $t } from '@vben/locales'; +import { cloneDeep } from '@vben/utils'; + +import { Tag } from 'ant-design-vue'; + +import { useVbenForm } from '#/adapter/form'; +import { findUserInfo, userAdd, userUpdate } from '#/api/system/user'; +import { authScopeOptions } from '#/views/system/role/data'; + +import { drawerSchema } from './data'; +import { deptList } from '#/api/system/dept/index'; +import { message, Upload } from 'ant-design-vue'; + +const emit = defineEmits<{ reload: [] }>(); + +const isUpdate = ref(false); //褰撳墠鏄惁涓虹紪杈� +const isLook = ref(false); //褰撳墠鏄惁涓烘煡鐪� +const title = computed(() => { + let text = ''; + if (isLook) { + text = $t('pages.common.look'); + } else if (isUpdate.value) { + text = $t('pages.common.edit'); + } else if (!isUpdate.value) { + text = $t('pages.common.add'); + } + return text; +}); + +const [BasicForm, formApi] = useVbenForm({ + commonConfig: { + formItemClass: 'col-span-2', + componentProps: { + class: 'w-full', + }, + labelWidth: 80, + }, + schema: drawerSchema(), + showDefaultActions: false, + wrapperClass: 'grid-cols-2', +}); + +/** + * 鐢熸垚瑙掕壊鐨勮嚜瀹氫箟label + * 涔熷彲浠ョ敤option鎻掓Ы鏉ュ仛 + * renderComponentContent: () => ({ + option: ({value, label, [disabled, key, title]}) => '', + }), + */ +function genRoleOptionlabel(role: Role) { + const found = authScopeOptions.find((item) => item.value === role.dataScope); + if (!found) { + return role.roleName; + } + return h('div', { class: 'flex items-center gap-[6px]' }, [ + h('span', null, role.roleName), + h(Tag, { color: found.color }, () => found.label), + ]); +} +// 鎵撳紑浠ュ強缂栬緫閮戒細璋冪敤璇ヤ簨浠� +const [BasicDrawer, drawerApi] = useVbenDrawer({ + onCancel: handleCancel, + onConfirm: handleConfirm, + async onOpenChange(isOpen) { + drawerApi.drawerLoading(true); + if (isOpen) { + let deptlist; + try { + // 鍒濆鍖栦竴涓嬭〃鍗曢�夐」 + deptlist = await ( + await deptList() + ).map((item) => { + return { + ...item, + value: item.deptId, + label: item.deptName, + }; + }); + } catch (error) { + console.log( "Api-deptList", error ) + } + formApi.updateSchema([ + { + componentProps: { options: deptlist }, + fieldName: 'responsibleDepartment', + }, + ]); + } + if (!isOpen) { + // 闇�瑕侀噸缃矖浣嶉�夋嫨 + formApi.updateSchema([ + { + componentProps: { options: [], placeholder: '璇峰厛閫夋嫨閮ㄩ棬' }, + fieldName: 'postIds', + }, + ]); + return null; + } + const { id } = drawerApi.getData() as { id?: number | string }; + const { look } = drawerApi.getData() as { look?: boolean }; + isUpdate.value = !!id; + isLook.value = !!look; + console.log('isLook.value', isLook.value); + /** update鏃� 绂佺敤鐢ㄦ埛鍚嶄慨鏀� 涓嶆樉绀哄瘑鐮佹 */ + // 濡傛灉涓烘煡鐪嬪垯鎵�鏈夐」閮戒笉鍙紪杈� + formApi.updateSchema([ + { componentProps: { disabled: isLook.value }, fieldName: 'workName' }, + { componentProps: { disabled: isLook.value }, fieldName: 'workClass' }, + { componentProps: { disabled: isLook.value }, fieldName: 'workContent' }, + { + componentProps: { disabled: isLook.value }, + fieldName: 'projectBudget', + }, + { + componentProps: { disabled: isLook.value }, + fieldName: 'amountProject', + }, + { + componentProps: { disabled: isLook.value }, + fieldName: 'responsibleDepartment', + }, + { componentProps: { disabled: isLook.value }, fieldName: 'Head' }, + { componentProps: { disabled: isLook.value }, fieldName: 'Annual' }, + { + componentProps: { disabled: isLook.value }, + fieldName: 'assessmentTime', + }, + { + componentProps: { disabled: isLook.value }, + fieldName: 'assessmentIndicators', + }, + { componentProps: { disabled: isLook.value }, fieldName: 'File' }, + ]); + + drawerApi.setState({ + showConfirmButton: !isLook.value, + }); + + // 鏇存柊 && 璧嬪�� + // const { postIds, posts, roleIds, roles, user } = await findUserInfo(id); //璋冪敤鎺ュ彛鑾峰彇璇︾粏淇℃伅 + + const data = { + id: '0', //宸ヤ綔id锛岀紪鍙� + workName: '宸ヤ綔鍚嶇О1', //宸ヤ綔鍚嶇О + workClass: '宸ヤ綔绫诲埆', //宸ヤ綔绫诲埆 + workContent: '宸ヤ綔鍐呭', //宸ヤ綔鍐呭 + projectBudget: '椤圭洰棰勭畻', //椤圭洰棰勭畻 + amountProject: '椤圭洰閲戦', //椤圭洰閲戦 + responsibleDepartment: '璐熻矗閮ㄩ棬', //璐熻矗閮ㄩ棬 + Head: '璐熻矗浜�', //璐熻矗浜� + Annual: '2025', //骞村害 + assessmentTime: '2025-06-25', //鑰冩牳鏃堕棿 + assessmentIndicators: '鑰冩牳鎸囨爣', //鑰冩牳鎸囨爣 + File: '鏂囦欢鍦板潃', //鏂囦欢涓婁紶 + taskStatus: '浠诲姟鐘舵��', //浠诲姟鐘舵�� + assignmentStatus: '鍒嗛厤鐘舵��', //鍒嗛厤鐘舵�� + workProgress: '宸ヤ綔杩涘害', //宸ヤ綔杩涘害 + }; + + console.log(data); + console.log(id); + + if (data && id) { + await Promise.all([ + // 娣诲姞鍩虹淇℃伅 + formApi.setValues(data), + ]); + } + drawerApi.drawerLoading(false); + console.log(isLook.value); + }, +}); +// 琛ㄥ崟鎻愪氦锛岀紪杈戜笌鏂板閮戒細璋冪敤杩欎釜鏂规硶浣� +async function handleConfirm() { + try { + drawerApi.drawerLoading(true); + const { valid } = await formApi.validate(); + if (!valid) { + return; + } + const data = cloneDeep(await formApi.getValues()); //琛ㄥ崟鍐呯殑鏁版嵁 + console.log(data); + // await (isUpdate.value ? userUpdate(data) : userAdd(data)); + emit('reload'); + await handleCancel(); + } catch (error) { + console.error(error); + } finally { + drawerApi.drawerLoading(false); + } +} + +async function handleCancel() { + drawerApi.close(); + await formApi.resetForm(); +} + +function IsShowConfirmButton(params: type) { + if (isLook.value) { + return false; + } + return true; +} + +// 鍦ㄨ繖鍔犺浇浜涙暟鎹� +</script> + +<template> + <BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]"> + <BasicForm /> + </BasicDrawer> +</template> diff --git a/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-import-modal.vue b/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-import-modal.vue new file mode 100644 index 0000000..fe81465 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-import-modal.vue @@ -0,0 +1,108 @@ +<script setup lang="ts"> +import type { UploadFile } from 'ant-design-vue/es/upload/interface'; + +import { h, ref, unref } from 'vue'; + +import { useVbenModal } from '@vben/common-ui'; +import { ExcelIcon, InBoxIcon } from '@vben/icons'; + +import { Modal, Switch, Upload } from 'ant-design-vue'; + +import { downloadImportTemplate, userImportData } from '#/api/system/user'; +import { commonDownloadExcel } from '#/utils/file/download'; + +const emit = defineEmits<{ reload: [] }>(); + +const UploadDragger = Upload.Dragger; + +const [BasicModal, modalApi] = useVbenModal({ + onCancel: handleCancel, + onConfirm: handleSubmit, +}); + +const fileList = ref<UploadFile[]>([]); +const checked = ref(false); + +async function handleSubmit() { + try { + modalApi.modalLoading(true); + if (fileList.value.length !== 1) { + handleCancel(); + return; + } + const data = { + file: fileList.value[0]!.originFileObj as Blob, + updateSupport: unref(checked), + }; + const { code, msg } = await userImportData(data); + let modal = Modal.success; + if (code === 200) { + emit('reload'); + } else { + modal = Modal.error; + } + handleCancel(); + modal({ + content: h('div', { + class: 'max-h-[260px] overflow-y-auto', + innerHTML: msg, // 鍚庡彴宸茬粡澶勭悊xss闂 + }), + title: '鎻愮ず', + }); + } catch (error) { + console.warn(error); + modalApi.close(); + } finally { + modalApi.modalLoading(false); + } +} + +function handleCancel() { + modalApi.close(); + fileList.value = []; + checked.value = false; +} +</script> + +<template> + <BasicModal + :close-on-click-modal="false" + :fullscreen-button="false" + title="鐢ㄦ埛瀵煎叆" + > + <!-- z-index涓嶈缃細閬尅妯℃澘涓嬭浇loading --> + <!-- 鎵嬪姩澶勭悊 鑰屼笉鏄斁鍏ユ枃浠跺氨涓婁紶 --> + <UploadDragger + v-model:file-list="fileList" + :before-upload="() => false" + :max-count="1" + :show-upload-list="true" + accept="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet, application/vnd.ms-excel" + > + <p class="ant-upload-drag-icon flex items-center justify-center"> + <InBoxIcon class="text-primary size-[48px]" /> + </p> + <p class="ant-upload-text">鐐瑰嚮鎴栬�呮嫋鎷藉埌姝ゅ涓婁紶鏂囦欢</p> + </UploadDragger> + <div class="mt-2 flex flex-col gap-2"> + <div class="flex items-center gap-2"> + <span>鍏佽瀵煎叆xlsx, xls鏂囦欢</span> + <a-button + type="link" + @click="commonDownloadExcel(downloadImportTemplate, '鐢ㄦ埛瀵煎叆妯℃澘')" + > + <div class="flex items-center gap-[4px]"> + <ExcelIcon /> + <span>涓嬭浇妯℃澘</span> + </div> + </a-button> + </div> + <div class="flex items-center gap-2"> + <span :class="{ 'text-red-500': checked }"> + 鏄惁鏇存柊/瑕嗙洊宸插瓨鍦ㄧ殑鐢ㄦ埛鏁版嵁 + </span> + <Switch v-model:checked="checked" /> + </div> + </div> + </BasicModal> +</template> diff --git a/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-info-modal.vue b/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-info-modal.vue new file mode 100644 index 0000000..63559af --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-info-modal.vue @@ -0,0 +1,62 @@ +<script setup lang="ts"> +import type { User } from '#/api/system/user/model'; + +import { useVbenModal } from '@vben/common-ui'; + +import { findUserInfo } from '#/api/system/user'; +import { Description, useDescription } from '#/components/description'; + +import { descSchema } from './info'; + +const [BasicModal, modalApi] = useVbenModal({ + onOpenChange: handleOpenChange, +}); + +const [registerDescription, { setDescProps }] = useDescription({ + column: 1, + labelStyle: { + minWidth: '150px', + width: '150px', + }, + schema: descSchema, +}); + +async function handleOpenChange(open: boolean) { + if (!open) { + return null; + } + modalApi.modalLoading(true); + + const { userId } = modalApi.getData() as { userId: number | string }; + const response = await findUserInfo(userId); + // 澶栭儴鐨剅oleIds postIds鎵嶆槸鐪熸瀵瑰簲鐨� 鏂板鏃朵负绌� + // posts鏈変负Null鐨勬儏鍐� 闇�瑕佺粰榛樿鍊� + const { postIds = [], posts = [], roleIds = [], roles = [], user } = response; + + const postNames = posts + .filter((item) => postIds.includes(item.postId)) + .map((item) => item.postName); + + const roleNames = roles + .filter((item) => roleIds.includes(item.roleId)) + .map((item) => item.roleName); + + interface UserWithNames extends User { + postNames: string[]; + roleNames: string[]; + } + (user as UserWithNames).postNames = postNames; + (user as UserWithNames).roleNames = roleNames; + + // 璧嬪�� + setDescProps({ data: user }); + + modalApi.modalLoading(false); +} +</script> + +<template> + <BasicModal :footer="false" :fullscreen-button="false" title="鐢ㄦ埛淇℃伅"> + <Description @register="registerDescription" /> + </BasicModal> +</template> diff --git a/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-report.vue b/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-report.vue new file mode 100644 index 0000000..93a20f6 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-report.vue @@ -0,0 +1,176 @@ +<script setup lang="ts"> +import type { Role } from '#/api/system/user/model'; + +import { computed, h, ref } from 'vue'; + +import { useVbenDrawer } from '@vben/common-ui'; +import { $t } from '@vben/locales'; +import { cloneDeep } from '@vben/utils'; + +import { Tag } from 'ant-design-vue'; + +import { useVbenForm } from '#/adapter/form'; +import { findUserInfo, userAdd, userUpdate } from '#/api/system/user'; +import { authScopeOptions } from '#/views/system/role/data'; + +import { drawerSchema, ReportScheme } from './data'; +import { deptList } from '#/api/system/dept/index'; + +const emit = defineEmits<{ reload: [] }>(); + +const isUpdate = ref(false); //褰撳墠鏄惁涓虹紪杈� +const isLook = ref(false); //褰撳墠鏄惁涓烘煡鐪� +const title = computed(() => { + let text = ''; + text="姹囨姤" + return text; +}); + +const [BasicForm, formApi] = useVbenForm({ + commonConfig: { + formItemClass: 'col-span-2', + componentProps: { + class: 'w-full', + }, + labelWidth: 80, + }, + schema: ReportScheme(), + showDefaultActions: false, + wrapperClass: 'grid-cols-2', +}); + +/** + * 鐢熸垚瑙掕壊鐨勮嚜瀹氫箟label + * 涔熷彲浠ョ敤option鎻掓Ы鏉ュ仛 + * renderComponentContent: () => ({ + option: ({value, label, [disabled, key, title]}) => '', + }), + */ +function genRoleOptionlabel(role: Role) { + const found = authScopeOptions.find((item) => item.value === role.dataScope); + if (!found) { + return role.roleName; + } + return h('div', { class: 'flex items-center gap-[6px]' }, [ + h('span', null, role.roleName), + h(Tag, { color: found.color }, () => found.label), + ]); +} +// 鎵撳紑浠ュ強缂栬緫閮戒細璋冪敤璇ヤ簨浠� +const [BasicDrawer, drawerApi] = useVbenDrawer({ + onCancel: handleCancel, + onConfirm: handleConfirm, + async onOpenChange(isOpen) { + drawerApi.drawerLoading(true); + if (isOpen) { + let deptlist; + try { + // 鍒濆鍖栦竴涓嬭〃鍗曢�夐」 + deptlist = await ( + await deptList() + ).map((item) => { + return { + ...item, + value: item.deptId, + label: item.deptName, + }; + }); + } catch (error) { + console.error('uReport-Api-deptlist', error); + } + formApi.updateSchema([ + { + componentProps: { options: deptlist }, + fieldName: 'responsibleDepartment', + }, + ]); + } + + if (!isOpen) { + // 闇�瑕侀噸缃矖浣嶉�夋嫨 + formApi.updateSchema([ + { + componentProps: { options: [], placeholder: '璇峰厛閫夋嫨閮ㄩ棬' }, + fieldName: 'postIds', + }, + ]); + return null; + } + const { id } = drawerApi.getData() as { id?: number | string }; + isUpdate.value = !!id; + /** update鏃� 绂佺敤鐢ㄦ埛鍚嶄慨鏀� 涓嶆樉绀哄瘑鐮佹 */ + + // 鏇存柊 && 璧嬪�� + // const { postIds, posts, roleIds, roles, user } = await findUserInfo(id); //璋冪敤鎺ュ彛鑾峰彇璇︾粏淇℃伅 + + const data = { + id: '0', //宸ヤ綔id锛岀紪鍙� + workName: '宸ヤ綔鍚嶇О1', //宸ヤ綔鍚嶇О + workClass: '宸ヤ綔绫诲埆', //宸ヤ綔绫诲埆 + workContent: '', //宸ヤ綔鍐呭 + projectBudget: '椤圭洰棰勭畻', //椤圭洰棰勭畻 + amountProject: '椤圭洰閲戦', //椤圭洰閲戦 + responsibleDepartment: '璐熻矗閮ㄩ棬', //璐熻矗閮ㄩ棬 + Head: '璐熻矗浜�', //璐熻矗浜� + Annual: '2025', //骞村害 + assessmentTime: '2025-06-25', //鑰冩牳鏃堕棿 + assessmentIndicators: '鑰冩牳鎸囨爣', //鑰冩牳鎸囨爣 + File: '鏂囦欢鍦板潃', //鏂囦欢涓婁紶 + taskStatus: '浠诲姟鐘舵��', //浠诲姟鐘舵�� + assignmentStatus: '鍒嗛厤鐘舵��', //鍒嗛厤鐘舵�� + workProgress: '宸ヤ綔杩涘害', //宸ヤ綔杩涘害 + }; + + console.log(data); + console.log(id); + + if (data && id) { + await Promise.all([ + // 娣诲姞鍩虹淇℃伅 + formApi.setValues(data), + ]); + } + drawerApi.drawerLoading(false); + console.log(isLook.value); + }, +}); +// 琛ㄥ崟鎻愪氦锛岀紪杈戜笌鏂板閮戒細璋冪敤杩欎釜鏂规硶浣� +async function handleConfirm() { + try { + drawerApi.drawerLoading(true); + const { valid } = await formApi.validate(); + if (!valid) { + return; + } + const data = cloneDeep(await formApi.getValues()); //琛ㄥ崟鍐呯殑鏁版嵁 + console.log(data); + // await (isUpdate.value ? userUpdate(data) : userAdd(data)); + emit('reload'); + await handleCancel(); + } catch (error) { + console.error(error); + } finally { + drawerApi.drawerLoading(false); + } +} + +async function handleCancel() { + drawerApi.close(); + await formApi.resetForm(); +} + +function IsShowConfirmButton(params: type) { + if (isLook.value) { + return false; + } + return true; +} + +// 鍦ㄨ繖鍔犺浇浜涙暟鎹� +</script> + +<template> + <BasicDrawer :close-on-click-modal="false" :title="title" class="w-[600px]"> + <BasicForm /> + </BasicDrawer> +</template> diff --git a/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-reset-pwd-modal.vue b/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-reset-pwd-modal.vue new file mode 100644 index 0000000..a3acf33 --- /dev/null +++ b/ruoyi-ui/apps/web-antd/src/views/work/myWork/user-reset-pwd-modal.vue @@ -0,0 +1,111 @@ +<script setup lang="ts"> +import type { ResetPwdParam, User } from '#/api/system/user/model'; + +import { useVbenModal, z } from '@vben/common-ui'; + +import { useVbenForm } from '#/adapter/form'; +import { userResetPassword } from '#/api/system/user'; +import { Description, useDescription } from '#/components/description'; + +const emit = defineEmits<{ reload: [] }>(); + +const [BasicModal, modalApi] = useVbenModal({ + onCancel: handleCancel, + onConfirm: handleSubmit, + onOpenChange: handleOpenChange, +}); + +const [registerDescription, { setDescProps }] = useDescription({ + column: 1, + schema: [ + { + field: 'userId', + label: '鐢ㄦ埛ID', + }, + { + field: 'userName', + label: '鐢ㄦ埛鍚�', + }, + { + field: 'nickName', + label: '鏄电О', + }, + ], +}); + +const [BasicForm, formApi] = useVbenForm({ + schema: [ + { + component: 'Input', + dependencies: { + show: () => false, + triggerFields: [''], + }, + fieldName: 'userId', + label: '鐢ㄦ埛ID', + rules: 'required', + }, + { + component: 'InputPassword', + componentProps: { + placeholder: '璇疯緭鍏ユ柊鐨勫瘑鐮�, 瀵嗙爜闀垮害涓�5 - 20', + }, + fieldName: 'password', + label: '鏂扮殑瀵嗙爜', + rules: z + .string() + .min(5, { message: '瀵嗙爜闀垮害涓�5 - 20' }) + .max(20, { message: '瀵嗙爜闀垮害涓�5 - 20' }), + }, + ], + showDefaultActions: false, + commonConfig: { + labelWidth: 80, + }, +}); + +async function handleOpenChange(open: boolean) { + if (!open) { + return null; + } + const { record } = modalApi.getData() as { record: User }; + setDescProps({ data: record }, true); + await formApi.setValues({ userId: record.userId }); +} + +async function handleSubmit() { + try { + modalApi.modalLoading(true); + const { valid } = await formApi.validate(); + if (!valid) { + return; + } + const data = await formApi.getValues(); + await userResetPassword(data as ResetPwdParam); + emit('reload'); + handleCancel(); + } catch (error) { + console.error(error); + } finally { + modalApi.modalLoading(false); + } +} + +async function handleCancel() { + modalApi.close(); + await formApi.resetForm(); +} +</script> + +<template> + <BasicModal + :close-on-click-modal="false" + :fullscreen-button="false" + title="閲嶇疆瀵嗙爜" + > + <div class="flex flex-col gap-[12px]"> + <Description @register="registerDescription" /> + <BasicForm /> + </div> + </BasicModal> +</template> diff --git a/ruoyi-ui/packages/@core/base/shared/src/constants/dict-enum.ts b/ruoyi-ui/packages/@core/base/shared/src/constants/dict-enum.ts index 16de04b..de6b4dd 100644 --- a/ruoyi-ui/packages/@core/base/shared/src/constants/dict-enum.ts +++ b/ruoyi-ui/packages/@core/base/shared/src/constants/dict-enum.ts @@ -15,4 +15,7 @@ WF_BUSINESS_STATUS = 'wf_business_status', // 涓氬姟鐘舵�� WF_FORM_TYPE = 'wf_form_type', // 琛ㄥ崟绫诲瀷 WF_TASK_STATUS = 'wf_task_status', // 浠诲姟鐘舵�� + TASK_STATUS = 'task_status', // 浠诲姟鐘舵�� + ASSIGNMENT_STATUS = 'assignment_status', // 鍒嗛厤鐘舵�� + ANNUAL = 'annual' //骞村害 } diff --git a/ruoyi-ui/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue b/ruoyi-ui/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue index 61852aa..ced202a 100644 --- a/ruoyi-ui/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue +++ b/ruoyi-ui/packages/@core/ui-kit/form-ui/src/form-render/form-field.vue @@ -366,7 +366,7 @@ </div> <Transition name="slide-up"> - <FormMessage class="absolute bottom-1" /> + <FormMessage class="absolute" /> </Transition> </div> </FormItem> diff --git a/ruoyi-ui/packages/locales/src/langs/en-US/common.json b/ruoyi-ui/packages/locales/src/langs/en-US/common.json index 440af82..67fc59d 100644 --- a/ruoyi-ui/packages/locales/src/langs/en-US/common.json +++ b/ruoyi-ui/packages/locales/src/langs/en-US/common.json @@ -15,6 +15,7 @@ "enabled": "Enabled", "disabled": "Disabled", "edit": "Edit", + "look": "Look", "delete": "Delete", "create": "Create", "yes": "Yes", diff --git a/ruoyi-ui/packages/locales/src/langs/zh-CN/common.json b/ruoyi-ui/packages/locales/src/langs/zh-CN/common.json index 95ec5f7..800af26 100644 --- a/ruoyi-ui/packages/locales/src/langs/zh-CN/common.json +++ b/ruoyi-ui/packages/locales/src/langs/zh-CN/common.json @@ -15,6 +15,7 @@ "enabled": "宸插惎鐢�", "disabled": "宸茬鐢�", "edit": "淇敼", + "look": "鏌ョ湅", "delete": "鍒犻櫎", "create": "鏂板", "yes": "鏄�", -- Gitblit v1.9.3