forked from mengyxu/noob-components
26 changed files with 1581 additions and 491 deletions
@ -0,0 +1,102 @@
@@ -0,0 +1,102 @@
|
||||
<template> |
||||
<el-container> |
||||
<el-main> |
||||
<ListTable |
||||
page |
||||
border |
||||
:data="tableData.rows" |
||||
:props="propsWithSelection" |
||||
:row-key="rowKey" |
||||
:example="tableData.example" |
||||
@query="tableData.query()" |
||||
> |
||||
<template v-for="{ code } in propsWithSlot" #[code]="{ row }"> |
||||
<slot :name="code" :row /> |
||||
</template> |
||||
<template #ui_selection="{ row }"> |
||||
<el-checkbox |
||||
:value="selection.get(row[rowKey])" |
||||
@update:model-value=" |
||||
(val) => { |
||||
if (val) { |
||||
selection.set(row[rowKey], row); |
||||
} else { |
||||
selection.delete(row[rowKey]); |
||||
} |
||||
} |
||||
" |
||||
/> |
||||
</template> |
||||
</ListTable> |
||||
</el-main> |
||||
<el-footer> |
||||
<ConfirmCancel @confirm="handleConfirm" @cancel="handleCancel" /> |
||||
</el-footer> |
||||
</el-container> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { ListTable, Element, AsyncHandler, useAsyncEmits, useListTable, useSysDict, ConfirmCancel } from "noob-mengyxu"; |
||||
import { computed, onMounted, reactive, ref } from "vue"; |
||||
import { useStore } from "vuex"; |
||||
import { useI18n } from "vue3-i18n"; |
||||
import { ListTableProps } from "../../../plugs/element/listTableDialog"; |
||||
import { PageResponse } from "../../../plugs/http"; |
||||
const { showMessage } = Element; |
||||
const { t } = useI18n(); |
||||
|
||||
defineOptions({ inheritAttrs: false }); |
||||
|
||||
type Row = Record<string, any>; |
||||
|
||||
const p = defineProps<ListTableProps>(); |
||||
|
||||
type Emits = { |
||||
query: [handler: AsyncHandler<PageResponse<Row>>, example: Record<string, any>]; |
||||
confirm: [handler: AsyncHandler<void>, rows: Row[]]; |
||||
close: [handler: AsyncHandler<void>]; |
||||
}; |
||||
const emits = defineEmits<Emits>(); |
||||
const emitsAsync = useAsyncEmits<Emits>(emits); |
||||
|
||||
const propsWithSelection = computed(() => [ |
||||
{ code: "ui_selection", slot: true, i18n: "common.select", width: 80 }, |
||||
...p.props, |
||||
]); |
||||
const propsWithSlot = computed(() => p.props.filter((col) => col.slot)); |
||||
|
||||
const store = useStore(); |
||||
|
||||
const { sysDict, updateDict } = useSysDict(); |
||||
|
||||
const tableData = useListTable({ |
||||
query: (example) => emitsAsync("query", example), |
||||
initialPage: p.initialPage, |
||||
initialPageSize: p.initialPageSize, |
||||
initExample: p.initExample, |
||||
disableAutoQuery: true, |
||||
}); |
||||
|
||||
const selection = reactive(new Map()); |
||||
|
||||
const handleConfirm = async () => { |
||||
try { |
||||
await emitsAsync("confirm", Array.from(selection.values())); |
||||
await emitsAsync("close"); |
||||
} catch { |
||||
showMessage("error", t("common.errors.submitError")); |
||||
} |
||||
}; |
||||
|
||||
const handleCancel = async () => { |
||||
await emitsAsync("close"); |
||||
}; |
||||
|
||||
onMounted(async () => { |
||||
if (p.useDicts) { |
||||
await updateDict(p.useDicts); |
||||
} |
||||
|
||||
await tableData.query(); |
||||
}); |
||||
</script> |
||||
@ -1,16 +1,32 @@
@@ -1,16 +1,32 @@
|
||||
import NoobTag from './item/tag.vue'; |
||||
import NoobButton from './item/button.vue'; |
||||
import NoobSelect from './item/select.vue'; |
||||
import NoobInput from './item/input.vue'; |
||||
import NoobDate from './item/datetime.vue'; |
||||
import LightBox from './item/light-box.vue'; |
||||
import NoobTag from "./item/tag.vue"; |
||||
import NoobButton from "./item/button.vue"; |
||||
import NoobSelect from "./item/select.vue"; |
||||
import NoobInput from "./item/input.vue"; |
||||
import NoobDate from "./item/datetime.vue"; |
||||
import LightBox from "./item/light-box.vue"; |
||||
import ButtonWithTooltip from "./item/buttonWithTooltip.vue"; |
||||
import ConfirmCancel from "./item/confirmCancel.vue"; |
||||
import TzDatePicker from "./item/tzDatePicker.vue"; |
||||
import TzDateTime from "./item/tzDateTime.vue"; |
||||
import SearchRow from "./data/search-row.vue"; |
||||
import ListTable from "./data/list-table.vue"; |
||||
import Infomation from "./data/infomation.vue"; |
||||
import ModifyForm from "./data/modify-form.vue"; |
||||
import Descriptions from "./data/descriptions.vue"; |
||||
import TableAction from "./data/table-action.vue"; |
||||
import ListTableDialog from "./data/listTableDialog.vue"; |
||||
|
||||
export { NoobTag, NoobButton, NoobSelect, NoobInput, NoobDate, LightBox }; |
||||
export { |
||||
NoobTag, |
||||
NoobButton, |
||||
NoobSelect, |
||||
NoobInput, |
||||
NoobDate, |
||||
LightBox, |
||||
ButtonWithTooltip, |
||||
TzDateTime, |
||||
TzDatePicker, |
||||
ConfirmCancel, |
||||
}; |
||||
|
||||
import SearchRow from './data/search-row.vue'; |
||||
import ListTable from './data/list-table.vue'; |
||||
import Infomation from './data/infomation.vue'; |
||||
import ModifyForm from './data/modify-form.vue'; |
||||
import Descriptions from './data/descriptions.vue'; |
||||
import TableAction from './data/table-action.vue'; |
||||
export { SearchRow, ListTable, Infomation, ModifyForm, Descriptions, TableAction }; |
||||
export { SearchRow, ListTable, ListTableDialog, Infomation, ModifyForm, Descriptions, TableAction }; |
||||
|
||||
@ -0,0 +1,46 @@
@@ -0,0 +1,46 @@
|
||||
<template> |
||||
<el-tooltip |
||||
v-if="props.tooltip" |
||||
:content="props.tooltip" |
||||
:disabled="props.disabled" |
||||
:show-after="0" |
||||
:hide-after="0" |
||||
:popper-options="{ modifiers: [{ name: 'eventListeners', enabled: false }] }" |
||||
popper-class="non-interactive-tooltip" |
||||
> |
||||
<NoobButton :disabled="props.disabled" v-bind="buttonProps"> |
||||
<slot /> |
||||
</NoobButton> |
||||
</el-tooltip> |
||||
<NoobButton v-else :disabled="props.disabled" v-bind="buttonProps"> |
||||
<slot /> |
||||
</NoobButton> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { useAttrs } from "vue"; |
||||
import { ElTooltip } from "element-plus"; |
||||
import { computed } from "vue"; |
||||
import { NoobButton } from "noob-mengyxu"; |
||||
|
||||
defineOptions({ inheritAttrs: false }); |
||||
|
||||
interface Props { |
||||
tooltip?: string; |
||||
disabled: boolean; |
||||
} |
||||
|
||||
const props = defineProps<Props>(); |
||||
const attrs = useAttrs(); |
||||
|
||||
const buttonProps = computed(() => { |
||||
const { class: _, ...rest } = attrs; |
||||
return rest; |
||||
}); |
||||
</script> |
||||
|
||||
<style> |
||||
.non-interactive-tooltip { |
||||
pointer-events: none !important; |
||||
} |
||||
</style> |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
<template> |
||||
<el-row> |
||||
<NoobButton type="primary" @click="emit('confirm')">{{ |
||||
t("base.confirm") |
||||
}}</NoobButton> |
||||
<el-space fill /> |
||||
<NoobButton type="info" @click="emit('cancel')">{{ |
||||
t("base.cancel") |
||||
}}</NoobButton> |
||||
</el-row> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { NoobButton } from "noob-mengyxu"; |
||||
import { useI18n } from "vue3-i18n"; |
||||
const { t } = useI18n(); |
||||
|
||||
const emit = defineEmits<{ |
||||
(e: "confirm"): void | Promise<void>; |
||||
(e: "cancel"): void | Promise<void>; |
||||
}>(); |
||||
</script> |
||||
@ -1,73 +1,87 @@
@@ -1,73 +1,87 @@
|
||||
<template> |
||||
<el-date-picker :size="state.size.size" :class="['form-item', full && 'full']" :value-format="formater" |
||||
v-model="myValue" :type="type" :placeholder="placeholder || t('rule.pleaseEnter')" :disabled="disabled" |
||||
:clearable="clearable" :teleported="false" /> |
||||
<el-date-picker |
||||
:size="state.size.size" |
||||
:class="['form-item', full && 'full']" |
||||
:value-format="formater" |
||||
v-model="myValue" |
||||
:type="type" |
||||
:placeholder="placeholder || t('rule.pleaseEnter')" |
||||
:disabled="disabled" |
||||
:clearable="clearable" |
||||
:teleported="false" |
||||
/> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { useStore } from "vuex"; |
||||
import { onMounted, ref, watch } from "vue"; |
||||
import { useI18n } from 'vue3-i18n'; |
||||
import { useI18n } from "vue3-i18n"; |
||||
const { t } = useI18n(); |
||||
const { state } = useStore(); |
||||
|
||||
const prop = defineProps({ |
||||
modelValue: null, |
||||
placeholder: { |
||||
type: String, |
||||
default: null |
||||
}, |
||||
type: { |
||||
type: String, |
||||
default: 'datetime' |
||||
}, |
||||
disabled: { |
||||
type: Boolean, |
||||
default: false, |
||||
}, |
||||
clearable: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
full: { |
||||
type: Boolean, |
||||
default: false, |
||||
}, |
||||
formater: { |
||||
type: String, |
||||
default: 'YYYY-MM-DD HH:mm:ss' |
||||
}, |
||||
width: { |
||||
type: Number |
||||
} |
||||
modelValue: null, |
||||
placeholder: { |
||||
type: String, |
||||
default: null, |
||||
}, |
||||
type: { |
||||
type: String, |
||||
default: "datetime", |
||||
}, |
||||
disabled: { |
||||
type: Boolean, |
||||
default: false, |
||||
}, |
||||
clearable: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
full: { |
||||
type: Boolean, |
||||
default: false, |
||||
}, |
||||
formater: { |
||||
type: String, |
||||
default: "YYYY-MM-DD HH:mm:ss", |
||||
}, |
||||
width: { |
||||
type: Number, |
||||
}, |
||||
}); |
||||
const emit = defineEmits(["update:modelValue"]); |
||||
const myValue = ref(null); |
||||
const width = ref('150px'); |
||||
const width = ref("150px"); |
||||
const setWidth = () => { |
||||
if (prop.width) { |
||||
width.value = prop.width + 'px'; |
||||
} else { |
||||
width.value = state.size.searchWidth; |
||||
} |
||||
} |
||||
watch(() => state.size, (n, o) => { |
||||
if (prop.width) { |
||||
width.value = prop.width + "px"; |
||||
} else { |
||||
width.value = state.size.searchWidth; |
||||
} |
||||
}; |
||||
watch( |
||||
() => state.size, |
||||
(n, o) => { |
||||
setWidth(); |
||||
}) |
||||
} |
||||
); |
||||
watch(myValue, (n, o) => { |
||||
emit('update:modelValue', n); |
||||
}) |
||||
watch(() => prop.modelValue, (n, o) => { |
||||
emit("update:modelValue", n); |
||||
}); |
||||
watch( |
||||
() => prop.modelValue, |
||||
(n, o) => { |
||||
myValue.value = n; |
||||
}) |
||||
} |
||||
); |
||||
onMounted(() => { |
||||
prop.modelValue && (myValue.value = prop.modelValue); |
||||
setWidth(); |
||||
prop.modelValue && (myValue.value = prop.modelValue); |
||||
setWidth(); |
||||
}); |
||||
</script> |
||||
<style lang="scss"> |
||||
//@import url(); 引入公共css类 |
||||
.form-item { |
||||
width: v-bind('width'); |
||||
width: v-bind("width"); |
||||
} |
||||
</style> |
||||
</style> |
||||
|
||||
@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
|
||||
<template> |
||||
<el-date-picker v-model="model"> </el-date-picker> |
||||
</template> |
||||
|
||||
<script setup lang="ts"> |
||||
import { ElDatePicker } from "element-plus"; |
||||
import dayjs, { type Dayjs } from "dayjs"; |
||||
import { nextTick, toRef, watch, watchEffect } from "vue"; |
||||
import { watchEffectOnce } from "../../../plugs/composables"; |
||||
|
||||
interface Props { |
||||
valueType: "iso8601" | "unix" | "unixMillis"; |
||||
tz?: string; |
||||
keepMillis?: boolean; |
||||
} |
||||
|
||||
const props = defineProps<Props>(); |
||||
|
||||
watchEffectOnce(toRef(props, "tz"), async () => { |
||||
const timeZone = await import("dayjs/plugin/timezone"); |
||||
dayjs.extend(timeZone.default); |
||||
}); |
||||
|
||||
const model = defineModel<string | number, string, Date, Date>({ |
||||
get: (value) => { |
||||
let dt: Dayjs; |
||||
if (props.valueType === "unix" || props.valueType === "unixMillis") { |
||||
if (typeof value === "string") { |
||||
value = parseFloat(value); |
||||
} |
||||
dt = props.valueType === "unix" ? dayjs.unix(value) : dayjs(value); |
||||
} else { |
||||
dt = dayjs(value); |
||||
} |
||||
|
||||
return dt.toDate(); |
||||
}, |
||||
set: (value) => { |
||||
const dt = dayjs(value); |
||||
if (props.valueType === "unix") { |
||||
return dt.unix(); |
||||
} else if (props.valueType === "unixMillis") { |
||||
return dt.valueOf(); |
||||
} else if (props.valueType === "iso8601" && props.tz) { |
||||
const fmt = props.keepMillis ? "YYYY-MM-DDTHH:mm:ss.SSSZ" : "YYYY-MM-DDTHH:mm:ssZ"; |
||||
return dt.tz(props.tz).format(fmt); |
||||
} else { |
||||
const isoWithMillis = dt.toISOString(); |
||||
if (props.keepMillis) return isoWithMillis; |
||||
else return isoWithMillis.slice(0, 19).concat("Z"); |
||||
} |
||||
}, |
||||
}); |
||||
</script> |
||||
@ -0,0 +1,55 @@
@@ -0,0 +1,55 @@
|
||||
<template> |
||||
{{ display }} |
||||
</template> |
||||
<script setup lang="ts"> |
||||
import dayjs from "dayjs"; |
||||
import { computed, toRef } from "vue"; |
||||
import { watchEffectOnce } from "noob-mengyxu"; |
||||
|
||||
interface Props { |
||||
value: string | number; |
||||
valueFormat?: string; |
||||
valueTz?: string; |
||||
displayFormat?: string; |
||||
locale?: string; |
||||
type?: "iso8601" | "unix" | "unixMillis"; |
||||
} |
||||
|
||||
const props = defineProps<Props>(); |
||||
|
||||
watchEffectOnce(toRef(props, "valueFormat"), async () => { |
||||
const customParseFormat = await import("dayjs/plugin/customParseFormat"); |
||||
dayjs.extend(customParseFormat.default); |
||||
}); |
||||
|
||||
watchEffectOnce(toRef(props, "valueTz"), async () => { |
||||
const timeZone = await import("dayjs/plugin/timezone"); |
||||
dayjs.extend(timeZone.default); |
||||
}); |
||||
|
||||
const parsed = computed(() => { |
||||
if (props.type === "unix" || props.type === "unixMillis") { |
||||
const value = typeof props.value === "string" ? parseFloat(props.value) : props.value; |
||||
return props.type === "unix" ? dayjs.unix(value) : dayjs(value); |
||||
} else { |
||||
if (props.valueFormat && props.valueTz) { |
||||
return dayjs.tz(props.value, props.valueFormat, props.valueTz); |
||||
} else if (props.valueTz) { |
||||
return dayjs.tz(props.value, props.valueTz); |
||||
} else if (props.valueFormat) { |
||||
return dayjs(props.value, props.valueFormat); |
||||
} else { |
||||
return dayjs(props.value); |
||||
} |
||||
} |
||||
}); |
||||
|
||||
const display = computed(() => { |
||||
let dt = parsed.value; |
||||
if (props.locale) { |
||||
dt = dt.locale(props.locale); |
||||
} |
||||
|
||||
return dt.format(props.displayFormat); |
||||
}); |
||||
</script> |
||||
@ -1,247 +1,241 @@
@@ -1,247 +1,241 @@
|
||||
<template> |
||||
<el-container :style="appMain" ref="main" v-show="!flag.loading"> |
||||
<el-header v-show="state.size.headHeight != '0px'" class="app-head" :height="state.size.headHeight"> |
||||
|
||||
<Head :title="title" :logo="logo" :username="username"> |
||||
<template #left> |
||||
<MenuTree v-show="mode == 'horizontal'" :data="menus" mode="horizontal" /> |
||||
</template> |
||||
<HeadPersonal @updatePwd="updatePwd" @logout="onLogout" :center="center" /> |
||||
<Fullscreen /> |
||||
<StyleChange v-if="styleAble" /> |
||||
<LangChange v-if="langAble" /> |
||||
<SizeChange v-if="sizeAble" /> |
||||
</Head> |
||||
|
||||
</el-header> |
||||
<el-container id="container"> |
||||
<el-aside v-show="mode == 'vertical' && state.size.asideWidth != '0px'" :width="state.size.asideWidth"> |
||||
<MenuTree :data="menus" mode="vertical" /> |
||||
</el-aside> |
||||
<el-main class="app-main"> |
||||
<router-view /> |
||||
</el-main> |
||||
</el-container> |
||||
<el-container :style="appMain" ref="main" v-show="!flag.loading"> |
||||
<el-header v-show="state.size.headHeight != '0px'" class="app-head" :height="state.size.headHeight"> |
||||
<Head :title="title" :logo="logo" :username="username"> |
||||
<template #left> |
||||
<MenuTree v-show="mode == 'horizontal'" :data="menus" mode="horizontal" /> |
||||
</template> |
||||
<HeadPersonal @updatePwd="updatePwd" @logout="onLogout" :center="center" /> |
||||
<Fullscreen /> |
||||
<StyleChange v-if="styleAble" /> |
||||
<LangChange v-if="langAble" /> |
||||
<SizeChange v-if="sizeAble" /> |
||||
</Head> |
||||
</el-header> |
||||
<el-container id="container"> |
||||
<el-aside v-show="mode == 'vertical' && state.size.asideWidth != '0px'" :width="state.size.asideWidth"> |
||||
<MenuTree :data="menus" mode="vertical" /> |
||||
</el-aside> |
||||
<el-main class="app-main"> |
||||
<router-view /> |
||||
</el-main> |
||||
</el-container> |
||||
</el-container> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { reactive, onMounted, ref, onBeforeUnmount } from "vue"; |
||||
import { onBeforeUnmount, onMounted, reactive, ref } from "vue"; |
||||
import { useStore } from "vuex"; |
||||
import { useRouter } from "vue-router"; |
||||
import { NoobHead, Api } from "noob-mengyxu" |
||||
const { Head, MenuTree, HeadPersonal, Fullscreen, StyleChange, LangChange, SizeChange } = NoobHead; |
||||
import { Api, NoobHead } from "noob-mengyxu"; |
||||
import md5 from "js-md5"; |
||||
|
||||
const { Head, MenuTree, HeadPersonal, Fullscreen, StyleChange, LangChange, SizeChange } = NoobHead; |
||||
|
||||
const { state, commit, dispatch } = useStore(); |
||||
const emit = defineEmits(['updatePwd', 'logout']); |
||||
const router = useRouter() |
||||
const emit = defineEmits(["updatePwd", "logout"]); |
||||
const router = useRouter(); |
||||
const appMain = reactive({ |
||||
height: window.innerHeight + 'px', |
||||
backgroundColor: state.style.bodyBg |
||||
height: window.innerHeight + "px", |
||||
backgroundColor: state.style.bodyBg, |
||||
}); |
||||
const main = ref(); |
||||
const flag = reactive({ |
||||
showHeader: true, |
||||
showAside: true, |
||||
loading: false |
||||
}) |
||||
showHeader: true, |
||||
showAside: true, |
||||
loading: false, |
||||
}); |
||||
let interval = 0; |
||||
|
||||
const props = defineProps({ |
||||
title: { |
||||
type: String, |
||||
default: null, |
||||
}, |
||||
menus: { |
||||
type: Array<any>(), |
||||
default: null |
||||
}, |
||||
mode: { |
||||
type: String, |
||||
default: 'vertical', |
||||
}, |
||||
styleAble: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
sizeAble: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
langAble: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
center: { |
||||
type: String, |
||||
default: 'home', |
||||
}, |
||||
checkUser: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
username: { |
||||
type: String, |
||||
default: null, |
||||
}, |
||||
closeAble: { |
||||
type: Boolean, |
||||
default: false, |
||||
}, |
||||
logo: { |
||||
type: String, |
||||
default: null, |
||||
}, |
||||
title: { |
||||
type: String, |
||||
default: null, |
||||
}, |
||||
menus: { |
||||
type: Array<any>(), |
||||
default: null, |
||||
}, |
||||
mode: { |
||||
type: String, |
||||
default: "vertical", |
||||
}, |
||||
styleAble: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
sizeAble: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
langAble: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
center: { |
||||
type: String, |
||||
default: "home", |
||||
}, |
||||
checkUser: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
username: { |
||||
type: String, |
||||
default: null, |
||||
}, |
||||
closeAble: { |
||||
type: Boolean, |
||||
default: false, |
||||
}, |
||||
logo: { |
||||
type: String, |
||||
default: null, |
||||
}, |
||||
}); |
||||
|
||||
const onResize = () => { |
||||
const height = window.innerHeight; |
||||
appMain.height = height + 'px'; |
||||
commit('initSize', [height, window.innerWidth]); |
||||
} |
||||
const height = window.innerHeight; |
||||
appMain.height = height + "px"; |
||||
commit("initSize", [height, window.innerWidth]); |
||||
}; |
||||
|
||||
const getUser = (first?) => { |
||||
if (!props.checkUser) { |
||||
return; |
||||
if (!props.checkUser) { |
||||
return; |
||||
} |
||||
first && (flag.loading = true); |
||||
Api.pub.getInfo().then((rsp) => { |
||||
first && (flag.loading = false); |
||||
if (rsp) { |
||||
if (state.user?.userId !== rsp.userId) { |
||||
commit("updateState", ["user", rsp]); |
||||
} |
||||
} else { |
||||
router.push("/login"); |
||||
} |
||||
first && (flag.loading = true); |
||||
Api.pub.getInfo().then((rsp) => { |
||||
first && (flag.loading = false); |
||||
if (rsp) { |
||||
if (state.user?.userId !== rsp.userId) { |
||||
commit('updateState', ['user', rsp]); |
||||
} |
||||
} else { |
||||
router.push('/login'); |
||||
} |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
const onLogout = () => { |
||||
Api.pub.logout().then(rsp => { |
||||
getUser(); |
||||
}); |
||||
} |
||||
Api.pub.logout().then((rsp) => { |
||||
getUser(); |
||||
}); |
||||
}; |
||||
|
||||
const updatePwd = pwd => { |
||||
pwd.old = md5(pwd.old); |
||||
pwd.new = md5(pwd.new); |
||||
pwd.reNew = md5(pwd.reNew); |
||||
emit('updatePwd', pwd); |
||||
Api.user.updatePwd(pwd); |
||||
} |
||||
const updatePwd = (pwd) => { |
||||
pwd.old = md5(pwd.old); |
||||
pwd.new = md5(pwd.new); |
||||
pwd.reNew = md5(pwd.reNew); |
||||
emit("updatePwd", pwd); |
||||
Api.user.updatePwd(pwd); |
||||
}; |
||||
|
||||
onMounted(() => { |
||||
router.push("/") |
||||
dispatch("getMenus"); |
||||
getUser(true); |
||||
interval = setInterval(getUser, 5000); |
||||
window.onresize = onResize; |
||||
onResize(); |
||||
router.push("/"); |
||||
dispatch("getMenus"); |
||||
getUser(true); |
||||
interval = setInterval(getUser, 5000); |
||||
window.onresize = onResize; |
||||
onResize(); |
||||
}); |
||||
|
||||
onBeforeUnmount(() => { |
||||
clearInterval(interval); |
||||
}) |
||||
|
||||
clearInterval(interval); |
||||
}); |
||||
</script> |
||||
|
||||
<style lang='scss'> |
||||
@charset "UTF-8"; |
||||
|
||||
<style lang="scss"> |
||||
body { |
||||
font-size: v-bind('state.size.fontSize') !important; |
||||
font-family: "Microsoft YaHei"; |
||||
width: 100%; |
||||
height: 100%; |
||||
overflow: hidden; |
||||
padding: 0px; |
||||
margin: 0px; |
||||
font-size: v-bind("state.size.fontSize") !important; |
||||
font-family: "Microsoft YaHei"; |
||||
width: 100%; |
||||
height: 100%; |
||||
overflow: hidden; |
||||
padding: 0px; |
||||
margin: 0px; |
||||
} |
||||
|
||||
.app-main { |
||||
box-shadow: 2px 2px 5px 3px #e5e6eb; |
||||
border-radius: 4px; |
||||
margin: 0px 0px 0px 3px !important; |
||||
padding: 0 !important; |
||||
height: v-bind('state.size.mainHeight'); |
||||
box-shadow: 2px 2px 5px 3px #e5e6eb; |
||||
border-radius: 4px; |
||||
margin: 0px 0px 0px 3px !important; |
||||
padding: 0 !important; |
||||
height: v-bind("state.size.mainHeight"); |
||||
} |
||||
|
||||
.el-header, |
||||
.main-head, |
||||
.main-table { |
||||
padding: 0; |
||||
padding: 0; |
||||
} |
||||
|
||||
#app, |
||||
#container, |
||||
.app-main { |
||||
background-color: v-bind('state.style.bodyBg'); |
||||
color: v-bind('state.style.color'); |
||||
font-size: v-bind('state.size.fontSize'); |
||||
background-color: v-bind("state.style.bodyBg"); |
||||
color: v-bind("state.style.color"); |
||||
font-size: v-bind("state.size.fontSize"); |
||||
} |
||||
|
||||
.app-head { |
||||
padding: 0px !important; |
||||
background-color: v-bind('state.style.headBg'); |
||||
height: v-bind('state.size.headHeight'); |
||||
padding: 0px !important; |
||||
background-color: v-bind("state.style.headBg"); |
||||
height: v-bind("state.size.headHeight"); |
||||
} |
||||
|
||||
.head-icon { |
||||
float: right; |
||||
cursor: pointer; |
||||
height: v-bind('state.size.headHeight'); |
||||
margin-right: 20px; |
||||
font-size: v-bind('state.size.headIconSize'); |
||||
align-items: center; |
||||
color: v-bind('state.style.color'); |
||||
float: right; |
||||
cursor: pointer; |
||||
height: v-bind("state.size.headHeight"); |
||||
margin-right: 20px; |
||||
font-size: v-bind("state.size.headIconSize"); |
||||
align-items: center; |
||||
color: v-bind("state.style.color"); |
||||
} |
||||
|
||||
.form-item { |
||||
margin-right: v-bind('state.size.searchMargin'); |
||||
margin-right: v-bind("state.size.searchMargin"); |
||||
|
||||
&.full { |
||||
width: 100%; |
||||
margin-right: 0px; |
||||
} |
||||
&.full { |
||||
width: 100%; |
||||
margin-right: 0px; |
||||
} |
||||
} |
||||
|
||||
#app { |
||||
|
||||
.el-input, |
||||
.el-textarea, |
||||
.el-date-editor, |
||||
.el-input__wrapper { |
||||
--el-input-bg-color: v-bind('state.style.itemBg') !important; |
||||
--el-fill-color-blank: v-bind('state.style.itemBg') !important; |
||||
--el-input-text-color: v-bind('state.style.color') !important; |
||||
} |
||||
|
||||
.el-popper { |
||||
--el-bg-color-overlay: v-bind('state.style.itemBg') !important; |
||||
--el-fill-color-light: v-bind('state.style.bodyBg') !important; |
||||
text-align: left; |
||||
} |
||||
|
||||
.el-dialog { |
||||
--el-dialog-bg-color: v-bind('state.style.bodyBg') !important; |
||||
} |
||||
|
||||
.el-drawer { |
||||
--el-drawer-bg-color: v-bind('state.style.bodyBg') !important; |
||||
} |
||||
|
||||
* { |
||||
--el-text-color-regular: : v-bind('state.style.color') !important; |
||||
--el-text-color-primary: v-bind('state.style.color') !important; |
||||
--el-disabled-bg-color: v-bind('state.style.itemBg') !important; |
||||
} |
||||
|
||||
*::selection { |
||||
background-color: v-bind('state.style.selectionBg'); |
||||
color: v-bind('state.style.selectionColor'); |
||||
} |
||||
.el-input, |
||||
.el-textarea, |
||||
.el-date-editor, |
||||
.el-input__wrapper { |
||||
--el-input-bg-color: v-bind("state.style.itemBg") !important; |
||||
--el-fill-color-blank: v-bind("state.style.itemBg") !important; |
||||
--el-input-text-color: v-bind("state.style.color") !important; |
||||
} |
||||
|
||||
.el-popper { |
||||
--el-bg-color-overlay: v-bind("state.style.itemBg") !important; |
||||
--el-fill-color-light: v-bind("state.style.bodyBg") !important; |
||||
text-align: left; |
||||
} |
||||
|
||||
.el-dialog { |
||||
--el-dialog-bg-color: v-bind("state.style.bodyBg") !important; |
||||
} |
||||
|
||||
.el-drawer { |
||||
--el-drawer-bg-color: v-bind("state.style.bodyBg") !important; |
||||
} |
||||
|
||||
* { |
||||
--el-text-color-regular: v-bind("state.style.color") !important; |
||||
--el-text-color-primary: v-bind("state.style.color") !important; |
||||
--el-disabled-bg-color: v-bind("state.style.itemBg") !important; |
||||
} |
||||
|
||||
*::selection { |
||||
background-color: v-bind("state.style.selectionBg"); |
||||
color: v-bind("state.style.selectionColor"); |
||||
} |
||||
} |
||||
|
||||
</style> |
||||
</style> |
||||
|
||||
@ -1,239 +1,234 @@
@@ -1,239 +1,234 @@
|
||||
<template> |
||||
<el-container :style="appMain" ref="main" v-show="!flag.loading"> |
||||
<el-aside :width="state.size.asideWidth"> |
||||
<MenuTree :title="title || t('title')" :logo="logo" :data="menus" mode="vertical" /> |
||||
</el-aside> |
||||
<el-container id="container"> |
||||
<el-header v-show="state.size.headHeight != '0px'" class="app-head" :height="state.size.headHeight"> |
||||
|
||||
<Head :title="title" :logo="logo" :username="username" :closeAble="true"> |
||||
<template #left> |
||||
<MenuTree v-show="state.size.asideWidth == '0px'" :data="menus" mode="horizontal" /> |
||||
</template> |
||||
<HeadPersonal @updatePwd="updatePwd" @logout="onLogout" :center="center" /> |
||||
<Fullscreen /> |
||||
<LangChange v-if="langAble" /> |
||||
<SizeChange v-if="sizeAble" /> |
||||
<slot></slot> |
||||
</Head> |
||||
</el-header> |
||||
<el-main class="app-main"> |
||||
<router-view /> |
||||
</el-main> |
||||
</el-container> |
||||
<el-container :style="appMain" ref="main" v-show="!flag.loading"> |
||||
<el-aside :width="state.size.asideWidth"> |
||||
<MenuTree :title="title || t('title')" :logo="logo" :data="menus" mode="vertical" /> |
||||
</el-aside> |
||||
<el-container id="container"> |
||||
<el-header v-show="state.size.headHeight != '0px'" class="app-head" :height="state.size.headHeight"> |
||||
<Head :title="title" :logo="logo" :username="username" :closeAble="true"> |
||||
<template #left> |
||||
<MenuTree v-show="state.size.asideWidth == '0px'" :data="menus" mode="horizontal" /> |
||||
</template> |
||||
<HeadPersonal @updatePwd="updatePwd" @logout="onLogout" :center="center" /> |
||||
<Fullscreen /> |
||||
<LangChange v-if="langAble" /> |
||||
<SizeChange v-if="sizeAble" /> |
||||
<slot></slot> |
||||
</Head> |
||||
</el-header> |
||||
<el-main class="app-main"> |
||||
<router-view /> |
||||
</el-main> |
||||
</el-container> |
||||
</el-container> |
||||
</template> |
||||
|
||||
<script lang="ts" setup> |
||||
import { reactive, onMounted, ref, onBeforeUnmount } from "vue"; |
||||
import { useStore } from "vuex"; |
||||
import { useRouter } from "vue-router"; |
||||
import { NoobHead, Api, Styles, Size } from "noob-mengyxu" |
||||
import { NoobHead, Api, Styles, Size } from "noob-mengyxu"; |
||||
const { Head, MenuTree, HeadPersonal, Fullscreen, StyleChange, LangChange, SizeChange } = NoobHead; |
||||
import md5 from "js-md5"; |
||||
import { useI18n } from "vue3-i18n"; |
||||
const { t } = useI18n(); |
||||
|
||||
const { state, commit, dispatch } = useStore(); |
||||
const emit = defineEmits(['updatePwd', 'logout']); |
||||
const router = useRouter() |
||||
const emit = defineEmits(["updatePwd", "logout"]); |
||||
const router = useRouter(); |
||||
const appMain = reactive({ |
||||
height: window.innerHeight + 'px', |
||||
backgroundColor: state.style.bodyBg |
||||
height: window.innerHeight + "px", |
||||
backgroundColor: state.style.bodyBg, |
||||
}); |
||||
const main = ref(); |
||||
const flag = reactive({ |
||||
showHeader: true, |
||||
showAside: true, |
||||
loading: false |
||||
}) |
||||
showHeader: true, |
||||
showAside: true, |
||||
loading: false, |
||||
}); |
||||
let interval = 0; |
||||
state.style = Styles.light; |
||||
|
||||
const props = defineProps({ |
||||
title: { |
||||
type: String, |
||||
default: null, |
||||
}, |
||||
menus: { |
||||
type: Array<any>(), |
||||
default: null |
||||
}, |
||||
styleAble: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
sizeAble: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
langAble: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
center: { |
||||
type: String, |
||||
default: 'home', |
||||
}, |
||||
checkUser: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
username: { |
||||
type: String, |
||||
default: null, |
||||
}, |
||||
logo: { |
||||
type: String, |
||||
default: null, |
||||
}, |
||||
title: { |
||||
type: String, |
||||
default: null, |
||||
}, |
||||
menus: { |
||||
type: Array<any>(), |
||||
default: null, |
||||
}, |
||||
styleAble: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
sizeAble: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
langAble: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
center: { |
||||
type: String, |
||||
default: "home", |
||||
}, |
||||
checkUser: { |
||||
type: Boolean, |
||||
default: true, |
||||
}, |
||||
username: { |
||||
type: String, |
||||
default: null, |
||||
}, |
||||
logo: { |
||||
type: String, |
||||
default: null, |
||||
}, |
||||
}); |
||||
|
||||
const onResize = () => { |
||||
const height = window.innerHeight; |
||||
appMain.height = height + 'px'; |
||||
commit('initSize', [height, window.innerWidth]); |
||||
} |
||||
const height = window.innerHeight; |
||||
appMain.height = height + "px"; |
||||
commit("initSize", [height, window.innerWidth]); |
||||
}; |
||||
|
||||
const getUser = (first?) => { |
||||
if (!props.checkUser) { |
||||
return; |
||||
if (!props.checkUser) { |
||||
return; |
||||
} |
||||
first && (flag.loading = true); |
||||
Api.pub.getInfo().then((rsp) => { |
||||
first && (flag.loading = false); |
||||
if (rsp) { |
||||
commit("updateState", ["user", rsp]); |
||||
} else { |
||||
router.push("/login"); |
||||
} |
||||
first && (flag.loading = true); |
||||
Api.pub.getInfo().then((rsp) => { |
||||
first && (flag.loading = false); |
||||
if (rsp) { |
||||
commit('updateState', ['user', rsp]); |
||||
} else { |
||||
router.push('/login'); |
||||
} |
||||
}); |
||||
}); |
||||
}; |
||||
|
||||
const onLogout = () => { |
||||
Api.pub.logout().then(rsp => { |
||||
getUser(); |
||||
}); |
||||
} |
||||
Api.pub.logout().then((rsp) => { |
||||
getUser(); |
||||
}); |
||||
}; |
||||
|
||||
const updatePwd = pwd => { |
||||
pwd.old = md5(pwd.old); |
||||
pwd.new = md5(pwd.new); |
||||
pwd.reNew = md5(pwd.reNew); |
||||
emit('updatePwd', pwd); |
||||
Api.user.updatePwd(pwd); |
||||
} |
||||
const updatePwd = (pwd) => { |
||||
pwd.old = md5(pwd.old); |
||||
pwd.new = md5(pwd.new); |
||||
pwd.reNew = md5(pwd.reNew); |
||||
emit("updatePwd", pwd); |
||||
Api.user.updatePwd(pwd); |
||||
}; |
||||
|
||||
onMounted(() => { |
||||
router.push("/") |
||||
dispatch("getMenus"); |
||||
getUser(true); |
||||
interval = setInterval(getUser, 5000); |
||||
window.onresize = onResize; |
||||
onResize(); |
||||
commit('updateState', ['style', Styles.plain]); |
||||
router.push("/"); |
||||
dispatch("getMenus"); |
||||
getUser(true); |
||||
interval = setInterval(getUser, 5000); |
||||
window.onresize = onResize; |
||||
onResize(); |
||||
commit("updateState", ["style", Styles.plain]); |
||||
}); |
||||
|
||||
onBeforeUnmount(() => { |
||||
clearInterval(interval); |
||||
}) |
||||
|
||||
clearInterval(interval); |
||||
}); |
||||
</script> |
||||
|
||||
<style lang='scss'> |
||||
@charset "UTF-8"; |
||||
|
||||
<style lang="scss"> |
||||
body { |
||||
font-size: v-bind('state.size.fontSize') !important; |
||||
font-family: "Microsoft YaHei"; |
||||
width: 100%; |
||||
height: 100%; |
||||
overflow: hidden; |
||||
padding: 0px; |
||||
margin: 0px; |
||||
font-size: v-bind("state.size.fontSize") !important; |
||||
font-family: "Microsoft YaHei"; |
||||
width: 100%; |
||||
height: 100%; |
||||
overflow: hidden; |
||||
padding: 0px; |
||||
margin: 0px; |
||||
} |
||||
|
||||
.app-main { |
||||
box-shadow: 2px 2px 5px 3px #e5e6eb; |
||||
border-radius: 4px; |
||||
margin: 0px 0px 0px 3px !important; |
||||
padding: 0 !important; |
||||
height: v-bind('state.size.mainHeight'); |
||||
box-shadow: 2px 2px 5px 3px #e5e6eb; |
||||
border-radius: 4px; |
||||
margin: 0px 0px 0px 3px !important; |
||||
padding: 0 !important; |
||||
height: v-bind("state.size.mainHeight"); |
||||
} |
||||
|
||||
.el-header, |
||||
.main-head, |
||||
.main-table { |
||||
padding: 0; |
||||
padding: 0; |
||||
} |
||||
|
||||
#app, |
||||
#container, |
||||
.app-main { |
||||
background-color: v-bind('state.style.bodyBg'); |
||||
color: v-bind('state.style.color'); |
||||
font-size: v-bind('state.size.fontSize'); |
||||
background-color: v-bind("state.style.bodyBg"); |
||||
color: v-bind("state.style.color"); |
||||
font-size: v-bind("state.size.fontSize"); |
||||
} |
||||
|
||||
.app-head { |
||||
padding: 0px !important; |
||||
background-color: v-bind('state.style.headBg'); |
||||
height: v-bind('state.size.headHeight'); |
||||
padding: 0px !important; |
||||
background-color: v-bind("state.style.headBg"); |
||||
height: v-bind("state.size.headHeight"); |
||||
} |
||||
|
||||
.head-icon { |
||||
float: right; |
||||
cursor: pointer; |
||||
height: v-bind('state.size.headHeight') !important; |
||||
margin-right: 20px !important; |
||||
font-size: v-bind('state.size.headIconSize') !important; |
||||
align-items: center !important; |
||||
color: v-bind('state.style.color') !important; |
||||
float: right; |
||||
cursor: pointer; |
||||
height: v-bind("state.size.headHeight") !important; |
||||
margin-right: 20px !important; |
||||
font-size: v-bind("state.size.headIconSize") !important; |
||||
align-items: center !important; |
||||
color: v-bind("state.style.color") !important; |
||||
} |
||||
|
||||
.form-item { |
||||
margin-right: v-bind('state.size.searchMargin'); |
||||
margin-right: v-bind("state.size.searchMargin"); |
||||
|
||||
&.full { |
||||
width: 100%; |
||||
margin-right: 0px; |
||||
} |
||||
&.full { |
||||
width: 100%; |
||||
margin-right: 0px; |
||||
} |
||||
} |
||||
|
||||
#app { |
||||
|
||||
.el-input, |
||||
.el-textarea, |
||||
.el-date-editor, |
||||
.el-input__wrapper { |
||||
--el-input-bg-color: v-bind('state.style.itemBg') !important; |
||||
--el-fill-color-blank: v-bind('state.style.itemBg') !important; |
||||
--el-input-text-color: v-bind('state.style.color') !important; |
||||
} |
||||
|
||||
.el-popper { |
||||
--el-bg-color-overlay: v-bind('state.style.itemBg') !important; |
||||
--el-fill-color-light: v-bind('state.style.bodyBg') !important; |
||||
text-align: left; |
||||
} |
||||
|
||||
.el-dialog { |
||||
--el-dialog-bg-color: v-bind('state.style.bodyBg') !important; |
||||
} |
||||
|
||||
.el-drawer { |
||||
--el-drawer-bg-color: v-bind('state.style.bodyBg') !important; |
||||
} |
||||
|
||||
* { |
||||
--el-text-color-regular: : v-bind('state.style.color') !important; |
||||
--el-text-color-primary: v-bind('state.style.color') !important; |
||||
--el-disabled-bg-color: v-bind('state.style.itemBg') !important; |
||||
} |
||||
|
||||
*::selection { |
||||
background-color: v-bind('state.style.selectionBg'); |
||||
color: v-bind('state.style.selectionColor'); |
||||
} |
||||
.el-input, |
||||
.el-textarea, |
||||
.el-date-editor, |
||||
.el-input__wrapper { |
||||
--el-input-bg-color: v-bind("state.style.itemBg") !important; |
||||
--el-fill-color-blank: v-bind("state.style.itemBg") !important; |
||||
--el-input-text-color: v-bind("state.style.color") !important; |
||||
} |
||||
|
||||
.el-popper { |
||||
--el-bg-color-overlay: v-bind("state.style.itemBg") !important; |
||||
--el-fill-color-light: v-bind("state.style.bodyBg") !important; |
||||
text-align: left; |
||||
} |
||||
|
||||
.el-dialog { |
||||
--el-dialog-bg-color: v-bind("state.style.bodyBg") !important; |
||||
} |
||||
|
||||
.el-drawer { |
||||
--el-drawer-bg-color: v-bind("state.style.bodyBg") !important; |
||||
} |
||||
|
||||
* { |
||||
--el-text-color-regular: v-bind("state.style.color") !important; |
||||
--el-text-color-primary: v-bind("state.style.color") !important; |
||||
--el-disabled-bg-color: v-bind("state.style.itemBg") !important; |
||||
} |
||||
|
||||
*::selection { |
||||
background-color: v-bind("state.style.selectionBg"); |
||||
color: v-bind("state.style.selectionColor"); |
||||
} |
||||
} |
||||
</style> |
||||
</style> |
||||
|
||||
@ -0,0 +1,11 @@
@@ -0,0 +1,11 @@
|
||||
<script setup lang="ts"> |
||||
|
||||
</script> |
||||
|
||||
<template> |
||||
|
||||
</template> |
||||
|
||||
<style scoped lang="scss"> |
||||
|
||||
</style> |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
export * from "./useActionPers"; |
||||
export * from "./useListTable"; |
||||
export * from "./useLoading"; |
||||
export * from "./useModifyForm"; |
||||
export * from "./useSysDict"; |
||||
export * from "./useWatchOnce"; |
||||
@ -0,0 +1,32 @@
@@ -0,0 +1,32 @@
|
||||
import { onMounted, toRef } from "vue"; |
||||
import { useRoute } from "vue-router"; |
||||
import { useStore } from "vuex"; |
||||
|
||||
export function useActionPers(parent?: string) { |
||||
const store = useStore(); |
||||
|
||||
if (!parent) { |
||||
const route = useRoute(); |
||||
parent = route.path; |
||||
} |
||||
|
||||
const update = async () => { |
||||
await store.dispatch("updateActionPers", parent); |
||||
}; |
||||
|
||||
const actionPers = toRef(() => { |
||||
// @ts-ignore
|
||||
const pers = store.state.actionPers[parent] ?? []; |
||||
return Object.fromEntries(pers.map((code) => [code, true])); |
||||
}); |
||||
|
||||
onMounted(async () => { |
||||
await update(); |
||||
}); |
||||
|
||||
return { |
||||
get: (per) => !!actionPers.value[per], |
||||
update, |
||||
actionPers, |
||||
}; |
||||
} |
||||
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
import { toReactive } from "@vueuse/core"; |
||||
import { reactive, ref, shallowRef, watchEffect } from "vue"; |
||||
import * as Element from "../element"; |
||||
import { useI18n } from "vue3-i18n"; |
||||
import { PageResponse } from "../http"; |
||||
|
||||
const { showMessage } = Element; |
||||
|
||||
export interface TableColumn { |
||||
code: string; |
||||
name?: string; |
||||
i18n?: string; |
||||
width: number; |
||||
dict?: string; |
||||
slot?: boolean; |
||||
|
||||
[others: string]: any; |
||||
} |
||||
|
||||
export interface TableData { |
||||
rows: PageResponse<any>; |
||||
example: Record<string, any>; |
||||
} |
||||
|
||||
interface Options { |
||||
query: (example: any) => Promise<PageResponse<any> | Record<string, any>[] | undefined>; |
||||
initialPage?: number; |
||||
initialPageSize?: number; |
||||
initExample?: Record<string, any>; |
||||
disableAutoQuery?: boolean; |
||||
deep?: boolean; |
||||
} |
||||
|
||||
export function useListTable(options: Options) { |
||||
const { t } = useI18n(); |
||||
const { initialPage, initialPageSize, initExample, deep } = options; |
||||
|
||||
type Row = Record<string, any>; |
||||
type Rows = Row[] | PageResponse<Row>; |
||||
|
||||
const empty = <T>() => [] as T[]; |
||||
const rows = deep ? ref<Rows>(empty()) : shallowRef<Rows>(empty()); |
||||
|
||||
const setRows = (resp?: Rows) => { |
||||
if (resp == null) { |
||||
rows.value = empty(); |
||||
} else { |
||||
rows.value = resp; |
||||
if (Array.isArray(resp) || resp.total == null) { |
||||
for (const key in pageParams) { |
||||
delete example[key]; |
||||
} |
||||
} |
||||
} |
||||
}; |
||||
|
||||
const pageParams = { |
||||
page: initialPage || 1, |
||||
size: initialPageSize || 10, |
||||
}; |
||||
|
||||
const example = reactive<any>({ |
||||
...pageParams, |
||||
...initExample, |
||||
}); |
||||
|
||||
const query = async () => { |
||||
try { |
||||
const resp = await options.query(example); |
||||
setRows(resp); |
||||
} catch (error) { |
||||
showMessage("error", t("common.errors.listTableQueryError")); |
||||
} |
||||
}; |
||||
|
||||
if (!options.disableAutoQuery) { |
||||
watchEffect(query); |
||||
} |
||||
|
||||
return { |
||||
rows: toReactive(rows), |
||||
example, |
||||
setRows, |
||||
query, |
||||
}; |
||||
} |
||||
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
// composables/useLoading.js
|
||||
import { ElLoading } from 'element-plus' |
||||
|
||||
export function useLoading() { |
||||
const showLoading = (options = {}) => { |
||||
return ElLoading.service({ |
||||
lock: true, |
||||
text: 'Loading', |
||||
spinner: 'el-icon-loading', |
||||
background: 'rgba(0, 0, 0, 0.3)', |
||||
...options |
||||
}) |
||||
} |
||||
|
||||
return { |
||||
showLoading |
||||
} |
||||
} |
||||
@ -0,0 +1,132 @@
@@ -0,0 +1,132 @@
|
||||
import { reactive, Ref } from "vue"; |
||||
import {} from "noob-mengyxu"; |
||||
import * as Element from "../element"; |
||||
import { clearAndAssign, deepCopy } from "../util/objectUtil"; |
||||
|
||||
const { showMessage } = Element; |
||||
|
||||
export interface FormProp { |
||||
code: string; |
||||
name?: string; |
||||
i18n?: string; |
||||
dict?: string; |
||||
slot?: boolean; |
||||
|
||||
[others: string]: any; |
||||
} |
||||
|
||||
interface Options { |
||||
props?: FormProp[]; |
||||
formRef?: Ref<any>; |
||||
handleAdd?: (value) => Promise<void>; |
||||
handleEdit?: (value) => Promise<void>; |
||||
handleCancel?: () => Promise<void>; |
||||
handleError?: (kind: "validation" | "internal", err?) => void; |
||||
init?: any; |
||||
extraProps?: string[] | "any" | ((code: string) => boolean); |
||||
} |
||||
|
||||
export function useModifyForm(options: Options) { |
||||
const { formRef, handleAdd, handleEdit, handleCancel, init, extraProps } = options; |
||||
const initValue = init ?? {}; |
||||
const props = options.props ?? []; |
||||
const propMap = Object.fromEntries(props.map((prop) => [prop.code, prop])); |
||||
|
||||
let isValidProp: (code: string) => boolean; |
||||
|
||||
if (typeof extraProps === "function") { |
||||
isValidProp = (code: string) => extraProps(code) as boolean; |
||||
} else if (Array.isArray(extraProps)) { |
||||
isValidProp = (code: string) => extraProps.includes(code); |
||||
} else { |
||||
isValidProp = (_code: string) => extraProps === "any"; |
||||
} |
||||
|
||||
const normalizeValue = (code: string, value: any) => { |
||||
const prop = propMap[code]; |
||||
if (!prop && !isValidProp(code)) return null; |
||||
if (prop?.dict) { |
||||
const parsed = parseInt(value); |
||||
return isNaN(parsed) ? value : parsed; |
||||
} |
||||
|
||||
return value; |
||||
}; |
||||
|
||||
const normalizeModel = (newModel: Record<string, any>) => |
||||
Object.fromEntries( |
||||
Object.entries(newModel).flatMap(([code, value]) => { |
||||
const normalized = normalizeValue(code, value); |
||||
return normalized != null ? [[code, normalized]] : []; |
||||
}) |
||||
); |
||||
|
||||
const handleError = |
||||
options.handleError ?? |
||||
((kind, err) => { |
||||
if (kind === "validation") { |
||||
showMessage("error", "Validation Error"); |
||||
} else if (kind === "internal") { |
||||
const msg = `Internal Error: ${err}`; |
||||
showMessage("error", msg); |
||||
console.error(msg); |
||||
} |
||||
}); |
||||
|
||||
return { |
||||
flagOnClose: null, |
||||
flagOnAdd: "add", |
||||
flagOnEdit: "edit", |
||||
|
||||
data: reactive<{ dialog: any; model: Record<string, any> }>({ |
||||
dialog: null, |
||||
model: { ...initValue }, |
||||
}), |
||||
|
||||
clearModel() { |
||||
clearAndAssign(this.data.model, initValue); |
||||
}, |
||||
|
||||
assignModel(newModel: Record<string, any>) { |
||||
const normalized = normalizeModel(newModel); |
||||
this.clearModel(); |
||||
Object.assign(this.data.model, normalized); |
||||
}, |
||||
|
||||
openAdd() { |
||||
this.clearModel(); |
||||
formRef?.value?.clearValidate(); |
||||
this.data.dialog = this.flagOnAdd; |
||||
}, |
||||
|
||||
openEdit(toEdit: Record<string, any>) { |
||||
this.assignModel(toEdit); |
||||
formRef?.value?.clearValidate(); |
||||
this.data.dialog = this.flagOnEdit; |
||||
}, |
||||
|
||||
close() { |
||||
this.data.dialog = this.flagOnClose; |
||||
}, |
||||
|
||||
async onConfirm() { |
||||
const value = deepCopy(this.data.model); |
||||
try { |
||||
if (this.data.dialog === this.flagOnAdd) { |
||||
await handleAdd?.(value); |
||||
} else if (this.data.dialog === this.flagOnEdit) { |
||||
await handleEdit?.(value); |
||||
} |
||||
} catch (err) { |
||||
handleError?.("internal", err); |
||||
} |
||||
|
||||
this.close(); |
||||
}, |
||||
|
||||
async onCancel() { |
||||
await handleCancel?.(); |
||||
this.close(); |
||||
}, |
||||
}; |
||||
} |
||||
@ -0,0 +1,43 @@
@@ -0,0 +1,43 @@
|
||||
import { computed } from "vue"; |
||||
import { useStore } from "vuex"; |
||||
|
||||
export function useSysDict() { |
||||
type Callback = () => Record<string, string> | Promise<Record<string, string>>; |
||||
const store = useStore(); |
||||
const callbacks: Record<string, Callback> = {}; |
||||
|
||||
const normalize = (content: Record<string, string>) => { |
||||
if (content == null) return {}; |
||||
return Object.fromEntries( |
||||
Object.entries(content).map(([k, v]) => { |
||||
const parsed = parseInt(v); |
||||
return [k, isNaN(parsed) ? v : parsed]; |
||||
}) |
||||
); |
||||
}; |
||||
|
||||
const updateDict = async (dictIds: string[], registerCallbacks: Record<string, Callback> = {}) => { |
||||
await store.dispatch("getDictMap", dictIds); |
||||
Object.assign(callbacks, registerCallbacks); |
||||
|
||||
for (const id of dictIds) { |
||||
const cb = callbacks[id]; |
||||
const resp = await cb?.(); |
||||
if (resp) { |
||||
store.commit("updateDict", [id, normalize(resp)]); |
||||
} |
||||
} |
||||
}; |
||||
|
||||
const unregister = (ids: string[]) => { |
||||
for (const id of ids) { |
||||
delete callbacks[id]; |
||||
} |
||||
}; |
||||
|
||||
return { |
||||
sysDict: computed<Record<string, Record<string, string>>>(() => store.state.dict), |
||||
updateDict, |
||||
unregister, |
||||
}; |
||||
} |
||||
@ -0,0 +1,12 @@
@@ -0,0 +1,12 @@
|
||||
import { nextTick, Ref, watchEffect, WatchEffect } from "vue"; |
||||
|
||||
export function watchEffectOnce(cond: Ref<any, any>, cb: WatchEffect) { |
||||
const stop = watchEffect((onCleanup) => { |
||||
if (cond.value) { |
||||
nextTick(() => stop()); |
||||
cb(onCleanup); |
||||
} |
||||
}); |
||||
|
||||
return stop; |
||||
} |
||||
@ -0,0 +1,68 @@
@@ -0,0 +1,68 @@
|
||||
import { type TableColumn, ListTableDialog, handleAsync } from "noob-mengyxu"; |
||||
import { ElMessageBox } from "element-plus"; |
||||
import { h } from "vue"; |
||||
import { PageResponse } from "../http"; |
||||
type Row = Record<string, any>; |
||||
|
||||
export interface ListTableProps { |
||||
props: TableColumn[]; |
||||
rowKey: string; |
||||
initialPage?: number; |
||||
initialPageSize?: number; |
||||
initExample?: Record<string, any>; |
||||
useDicts?: string[]; |
||||
} |
||||
|
||||
interface Options { |
||||
title: string; |
||||
props: ListTableProps; |
||||
query: (example: Record<string, any>) => Promise<PageResponse<Row>>; |
||||
confirm: (rows: Row[]) => Promise<void>; |
||||
children?: any; |
||||
} |
||||
|
||||
export function showListTableDialog({ title, props, query, confirm, children }: Options) { |
||||
const open = async () => { |
||||
try { |
||||
await ElMessageBox({ |
||||
title, |
||||
showConfirmButton: false, |
||||
showCancelButton: false, |
||||
message: h( |
||||
ListTableDialog, |
||||
{ |
||||
...props, |
||||
onQuery: handleAsync(query), |
||||
onConfirm: handleAsync(async (rows) => { |
||||
console.log("onConfirm"); |
||||
await confirm(rows); |
||||
}), |
||||
onClose: handleAsync(async () => { |
||||
console.log("onClose"); |
||||
ElMessageBox.close(); |
||||
}), |
||||
}, |
||||
children |
||||
), |
||||
customStyle: { |
||||
maxWidth: "80%", |
||||
}, |
||||
}); |
||||
} catch (err) { |
||||
if (err === "cancel" || err === "close") { |
||||
return; |
||||
} |
||||
|
||||
throw err; |
||||
} |
||||
}; |
||||
|
||||
const close = () => { |
||||
ElMessageBox.close(); |
||||
}; |
||||
|
||||
return { |
||||
open, |
||||
close, |
||||
}; |
||||
} |
||||
@ -0,0 +1,318 @@
@@ -0,0 +1,318 @@
|
||||
/* eslint-disable */ |
||||
import axios from "axios"; |
||||
import * as Lang from "../i18n"; |
||||
import { loading, close, showMessage } from "../element"; |
||||
const { t } = Lang.i18n; |
||||
|
||||
let router; |
||||
|
||||
const config = { |
||||
baseURL: process.env.VUE_APP_BASE_URL ? "/api" : "", |
||||
timeout: 60 * 1000, |
||||
withCredentials: true, // Check cross-site Access-Control
|
||||
}; |
||||
|
||||
const _axios = axios.create(config); |
||||
_axios.defaults.headers.post["Content-Type"] = "application/json;charset=UTF-8"; |
||||
_axios.defaults.headers.put["Content-Type"] = "application/json;charset=UTF-8"; |
||||
_axios.defaults.headers.delete["Content-Type"] = "application/json;charset=UTF-8"; |
||||
|
||||
export const registerBaseUrl = (url) => { |
||||
_axios.defaults.baseURL = url; |
||||
}; |
||||
|
||||
export const registerRouter = (routerP) => { |
||||
router = routerP; |
||||
}; |
||||
|
||||
// Add a request interceptor
|
||||
_axios.interceptors.request.use( |
||||
function (config) { |
||||
const time = new Date().getTime().toString(); |
||||
const params = config.params; |
||||
if (params) { |
||||
delEmpty(params); |
||||
params.t = time; |
||||
} |
||||
const data = config.data; |
||||
if (data != null && typeof data === "object") { |
||||
delEmpty(data); |
||||
data.t = time; |
||||
} |
||||
return config; |
||||
}, |
||||
function (error) { |
||||
return Promise.reject(error); |
||||
} |
||||
); |
||||
|
||||
function delEmpty(data) { |
||||
if (data) { |
||||
for (const item in data) { |
||||
if (data.hasOwnProperty(item)) { |
||||
const val = data[item]; |
||||
if ((val == null || val == "null" || val == "") && val !== 0 && val !== false) { |
||||
delete data[item]; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
// Add a response interceptor
|
||||
_axios.interceptors.response.use( |
||||
function (response) { |
||||
return response.data; |
||||
}, |
||||
function (error) { |
||||
return Promise.reject(error); |
||||
} |
||||
); |
||||
|
||||
type QueryParams = URLSearchParams | Record<string, any> | ConstructorParameters<typeof URLSearchParams>[0]; |
||||
|
||||
export interface AxiosOptions { |
||||
noMsg?: boolean | null; |
||||
noLoading?: boolean | null; |
||||
filter?: (response) => any; |
||||
query?: QueryParams; |
||||
} |
||||
|
||||
const defaultFilterResp = (resp) => { |
||||
const isSuccess = resp.success || resp.status === "ok" || resp.status === "success" || resp.data != null; |
||||
const isError = resp.success === false || resp.status === "error"; |
||||
if (isSuccess) { |
||||
return resp.data ? resp.data : resp; |
||||
} else if (isError) { |
||||
return false; |
||||
} else return resp; |
||||
}; |
||||
|
||||
export function post(url, data?, options: AxiosOptions = {}) { |
||||
return new Promise((resolve, reject) => { |
||||
if (!options.noLoading) { |
||||
loading(); |
||||
} |
||||
_axios.post(url, data).then( |
||||
(response: any) => { |
||||
handResponse(response, resolve, options); |
||||
}, |
||||
(err) => { |
||||
handError(err, reject, options); |
||||
} |
||||
); |
||||
}); |
||||
} |
||||
|
||||
function mergeQueryParams(...params: Array<QueryParams>) { |
||||
const res: URLSearchParams = new URLSearchParams(); |
||||
for (const param of params) { |
||||
if (param == null) continue; |
||||
let parsed; |
||||
if (param instanceof URLSearchParams) { |
||||
parsed = param; |
||||
} else { |
||||
parsed = new URLSearchParams(param); |
||||
} |
||||
|
||||
parsed |
||||
.entries() |
||||
.filter(([k, v]) => v != null && v != "null" && v != "undefined" && v != "") |
||||
.forEach(([k, v]) => res?.append(k, v)); |
||||
} |
||||
|
||||
return res ?? new URLSearchParams(); |
||||
} |
||||
|
||||
export function get(url, data?: QueryParams, options: AxiosOptions = {}) { |
||||
return new Promise((resolve, reject) => { |
||||
if (!options.noLoading) { |
||||
loading(); |
||||
} |
||||
_axios.get(url, { params: mergeQueryParams(data, options.query) }).then( |
||||
(response: any) => { |
||||
handResponse(response, resolve, options); |
||||
}, |
||||
(err) => { |
||||
handError(err, reject, options); |
||||
} |
||||
); |
||||
}); |
||||
} |
||||
|
||||
export function put(url, data?, options: AxiosOptions = {}) { |
||||
return new Promise((resolve, reject) => { |
||||
if (!options.noLoading) { |
||||
loading(); |
||||
} |
||||
_axios.put(url, data, { params: mergeQueryParams(options.query) }).then( |
||||
(response: any) => { |
||||
handResponse(response, resolve, options); |
||||
}, |
||||
(err) => { |
||||
handError(err, reject, options); |
||||
} |
||||
); |
||||
}); |
||||
} |
||||
|
||||
export function delate(url, data?, options: AxiosOptions = {}) { |
||||
return new Promise((resolve, reject) => { |
||||
if (!options.noLoading) { |
||||
loading(); |
||||
} |
||||
_axios({ |
||||
method: "delete", |
||||
url: url, |
||||
data: data, |
||||
params: mergeQueryParams(options.query), |
||||
}).then( |
||||
(response: any) => { |
||||
handResponse(response, resolve, options); |
||||
}, |
||||
(err) => { |
||||
handError(err, reject, options); |
||||
} |
||||
); |
||||
}); |
||||
} |
||||
|
||||
function handResponse(response, resolve, options: AxiosOptions = {}) { |
||||
if (!options.noLoading) { |
||||
close(); |
||||
} |
||||
|
||||
const filterResponse = options.filter ?? defaultFilterResp; |
||||
const result = filterResponse(response); |
||||
|
||||
if (result) { |
||||
if (!options.noMsg && response.message) { |
||||
showMessage("success", response.message); |
||||
} |
||||
|
||||
resolve(result); |
||||
} else { |
||||
if (response.message == "session timeout") { |
||||
router?.push("/login"); |
||||
response.message = t("http.unLogin"); |
||||
} |
||||
if (response.message == "no permission") { |
||||
response.message = t("http.unPermission"); |
||||
} |
||||
if (response.message?.indexOf("no permission for ") == 0) { |
||||
response.message = t("http.noPermission"); |
||||
} |
||||
if (!options.noMsg && response.message) { |
||||
showMessage("error", response.message); |
||||
} |
||||
response.error && console.log(response.error); |
||||
resolve(false); |
||||
} |
||||
} |
||||
|
||||
function handError(err, reject, options: AxiosOptions = {}) { |
||||
if (!options.noLoading) { |
||||
close(); |
||||
} |
||||
if (!options.noMsg) { |
||||
showMessage("error", t("http.error")); |
||||
} |
||||
reject(err); |
||||
} |
||||
|
||||
export function upload(file, url, data) { |
||||
return new Promise((resolve, reject) => { |
||||
let param = new FormData(); // 创建form对象
|
||||
param.append("file", file); // 通过append向form对象添加数据
|
||||
if (data) { |
||||
for (const item in data) { |
||||
if (data.hasOwnProperty(item)) { |
||||
param.append(item, data[item]); // 添加form表单中其他数据
|
||||
} |
||||
} |
||||
} |
||||
let config = { |
||||
headers: { "Content-Type": "multipart/form-data" }, |
||||
}; |
||||
_axios.post(url, param, config).then( |
||||
(response) => { |
||||
handResponse(response, resolve); |
||||
}, |
||||
(err) => { |
||||
handError(err, reject); |
||||
} |
||||
); |
||||
}); |
||||
} |
||||
|
||||
export function getFile(url, data?, options: AxiosOptions = {}) { |
||||
return new Promise((resolve, reject) => { |
||||
if (!options.noLoading) { |
||||
loading(); |
||||
} |
||||
_axios({ |
||||
method: "get", |
||||
url: url, // 请求地址
|
||||
params: data, // 参数
|
||||
responseType: "blob", // 表明返回服务器返回的数据类型
|
||||
}).then( |
||||
(response: any) => { |
||||
handResponse(response, resolve, options); |
||||
}, |
||||
(err) => { |
||||
handError(err, reject, options); |
||||
} |
||||
); |
||||
}); |
||||
} |
||||
|
||||
export function download(fileName, url, data = {}, callBack?) { |
||||
loading(); |
||||
_axios({ |
||||
method: "get", |
||||
url: url, // 请求地址
|
||||
params: data, // 参数
|
||||
responseType: "blob", // 表明返回服务器返回的数据类型
|
||||
}).then( |
||||
(response: any) => { |
||||
close(); |
||||
const reader = new FileReader() as any; |
||||
reader.readAsText(response); |
||||
reader.onload = function () { |
||||
try { |
||||
const result = JSON.parse(reader.result); |
||||
if (typeof result === "object") { |
||||
showMessage("error", result.message, false); |
||||
if (callBack != null) { |
||||
callBack(false); |
||||
} |
||||
return; |
||||
} |
||||
} catch (err) {} |
||||
const blob = new Blob([response], { |
||||
type: "application/octet-stream", |
||||
}); |
||||
const navigator = window.navigator as any; |
||||
if (navigator.msSaveOrOpenBlob) { |
||||
navigator.msSaveBlob(blob, fileName); |
||||
} else { |
||||
const link = document.createElement("a"); |
||||
link.href = window.URL.createObjectURL(blob); |
||||
link.download = fileName; |
||||
link.click(); |
||||
window.URL.revokeObjectURL(link.href); |
||||
} |
||||
if (callBack != null) { |
||||
callBack(true); |
||||
} |
||||
}; |
||||
}, |
||||
(err) => { |
||||
close(); |
||||
showMessage("error", t("http.downFail")); |
||||
if (callBack != null) { |
||||
callBack(false); |
||||
} |
||||
} |
||||
); |
||||
} |
||||
@ -1,2 +1,4 @@
@@ -1,2 +1,4 @@
|
||||
export * as Axios from './axios'; |
||||
export * as Axios2 from './axios2'; |
||||
export * as Axios from "./axios"; |
||||
export * as Axios2 from "./axios2"; |
||||
export * as Axios3 from "./axios3"; |
||||
export * from "./misc"; |
||||
|
||||
@ -0,0 +1,10 @@
@@ -0,0 +1,10 @@
|
||||
export interface PageResponse<T> { |
||||
data: T[]; |
||||
page?: number; |
||||
total: number; |
||||
} |
||||
|
||||
export const pageEmpty = <T>(): PageResponse<T> => ({ |
||||
data: [], |
||||
total: 0, |
||||
}); |
||||
@ -1,7 +1,10 @@
@@ -1,7 +1,10 @@
|
||||
export * from './config'; |
||||
export * as Element from './element'; |
||||
export * as Store from './store'; |
||||
export * as Http from './http'; |
||||
export * as Lang from './i18n'; |
||||
export * as Api from './api'; |
||||
export * from './constant'; |
||||
export * from "./config"; |
||||
export * as Element from "./element"; |
||||
export * as Store from "./store"; |
||||
export * as Http from "./http"; |
||||
export * as Lang from "./i18n"; |
||||
export * as Api from "./api"; |
||||
export * from "./constant"; |
||||
export * from "./util/asyncUtil"; |
||||
export * from "./util/objectUtil"; |
||||
export * from "./composables"; |
||||
|
||||
@ -0,0 +1,21 @@
@@ -0,0 +1,21 @@
|
||||
export type AsyncHandler<T> = { |
||||
resolve: (t: T | PromiseLike<T>) => void; |
||||
reject: (err: any) => void; |
||||
}; |
||||
|
||||
type ExtractResult<Args extends any[]> = Args extends [AsyncHandler<infer T>, ...any[]] ? T : never; |
||||
|
||||
type ExtractArgs<Args extends any[]> = Args extends [AsyncHandler<any>, ...infer Rest] ? Rest : never; |
||||
|
||||
export function useAsyncEmits<Emits extends Record<string, any[]>>(emits: (evt: any, ...args: any[]) => void) { |
||||
const emitsAsync = <Evt extends keyof Emits>(evt: Evt, ...args: ExtractArgs<Emits[Evt]>) => |
||||
new Promise<ExtractResult<Emits[Evt]>>((resolve, reject) => { |
||||
emits(evt, { resolve, reject }, ...args); |
||||
}); |
||||
|
||||
return emitsAsync; |
||||
} |
||||
|
||||
export function handleAsync<T, Args extends any[]>(handler: (...args: Args) => Promise<T>) { |
||||
return ({ resolve, reject }: AsyncHandler<T>, ...args: Args) => handler(...args).then(resolve, reject); |
||||
} |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
import { cloneDeep } from "lodash-es"; |
||||
|
||||
export function deepCopy<T = any>(obj: T): T { |
||||
return cloneDeep(obj); |
||||
} |
||||
|
||||
export function clearObject(obj: Record<string, any>) { |
||||
for (const key in obj) { |
||||
delete obj[key]; |
||||
} |
||||
} |
||||
|
||||
export function clearAndAssign(target: Record<string, any>, source: Record<string, any>) { |
||||
clearObject(target); |
||||
Object.assign(target, source); |
||||
} |
||||
|
||||
export function unnest(obj: Record<string, any>, key: string, prefix: string) { |
||||
const { [key]: toFlatten, ...rest } = obj; |
||||
const prefixed = Object.fromEntries(Object.entries(toFlatten).map(([k, v]) => [prefix.concat(k), v])); |
||||
return { ...rest, ...prefixed }; |
||||
} |
||||
Loading…
Reference in new issue