page: 📄 多语言页面待完成

This commit is contained in:
bunny 2024-09-29 16:52:09 +08:00
parent 024188ee3d
commit 1d98efe92e
32 changed files with 1319 additions and 1189 deletions

View File

@ -7,6 +7,17 @@ export interface BaseResult<T> {
message: string; message: string;
} }
export interface ResultTable {
/** 列表数据 */
list: Array<any>;
/** 总条目数 */
total?: number;
/** 每页显示条目个数 */
pageSize?: number;
/** 当前页数 */
pageNo?: number;
}
export type resultType = { export type resultType = {
accessToken?: string; accessToken?: string;
}; };

View File

@ -1,9 +1,65 @@
import { http } from '@/api/service/mockRequest'; import { http } from '@/api/service/mockRequest';
import type { Result } from '@/types/store/baseStoreState'; import type { BaseResult } from '@/api/service/types';
/** /**
* * * *
*/ */
export const fetchGetI18n = () => { export const fetchGetI18n = () => {
return http.request<Result<object>>('get', 'getI18n'); return http.request<BaseResult<object>>('get', 'i18n/getI18n');
};
/**
* ---
*/
export const fetchGetI18nList = (data: any) => {
return http.request<BaseResult<object>>('get', `i18n/getI18nList/${data.page}/${data.pageSize}`, { data });
};
/**
* ---
*/
export const fetchAddI18n = (data: any) => {
return http.request<BaseResult<object>>('post', 'i18n/addI18n', { data });
};
/**
* ---
*/
export const fetchUpdateI18n = (data: any) => {
return http.request<BaseResult<object>>('put', 'i18n/updateI18n', { data });
};
/**
* ---
*/
export const fetchDeleteI18n = (data: any) => {
return http.request<BaseResult<object>>('put', 'i18n/deleteI18n', { data });
};
/**
* ---
*/
export const fetchGetI18nTypeList = () => {
return http.request<BaseResult<object>>('get', 'i18nType/getI18nTypeList');
};
/**
* ---
*/
export const fetchAddI18nType = (data: any) => {
return http.request<BaseResult<object>>('post', 'i18nType/addI18nType', { data });
};
/**
* ---
*/
export const fetchUpdateI18nType = (data: any) => {
return http.request<BaseResult<object>>('put', 'i18nType/updateI18nType', { data });
};
/**
* ---
*/
export const fetchDeleteI18nType = (data: any) => {
return http.request<BaseResult<object>>('put', 'i18nType/deleteI18nType', { data });
}; };

View File

@ -1,38 +1,32 @@
import { http } from '@/api/service/request'; import { http } from '@/api/service/request';
import type { BaseResult } from '@/api/service/types'; import type { BaseResult, ResultTable } from '@/api/service/types';
type ResultTable = {
/** 列表数据 */
list: Array<any>;
/** 总条目数 */
total?: number;
/** 每页显示条目个数 */
pageSize?: number;
/** 当前页数 */
pageNo?: number;
};
/** 系统管理-用户路由获取 */ /** 系统管理-用户路由获取 */
export const getRouterAsync = () => { export const getRouterAsync = () => {
return http.request<BaseResult<any>>('get', 'router/getRouterAsync'); return http.request<BaseResult<any>>('get', 'router/getRouterAsync');
}; };
/** 系统管理-菜单管理列表 */ /** 图标管理-获取系统图标 */
export const getMenuIconList = (data: any) => {
return http.request<BaseResult<ResultTable>>('get', `menuIcon/getMenuIconList/${data.page}/${data.limit}`, { data });
};
/** 菜单管理-列表 */
export const getMenuList = (data?: any) => { export const getMenuList = (data?: any) => {
return http.request<BaseResult<ResultTable>>('get', `router/getMenus`, { data }); return http.request<BaseResult<ResultTable>>('get', `router/getMenus`, { data });
}; };
/** 系统管理-添加菜单 */ /** 菜单管理-添加菜单 */
export const addMenu = (data?: any) => { export const addMenu = (data?: any) => {
return http.request<BaseResult<any>>('post', `router/addMenu`, { data }); return http.request<BaseResult<any>>('post', `router/addMenu`, { data });
}; };
/** 系统管理-更新菜单 */ /** 菜单管理-更新菜单 */
export const updateMenu = (data?: any) => { export const updateMenu = (data?: any) => {
return http.request<BaseResult<any>>('put', `router/updateMenu`, { data }); return http.request<BaseResult<any>>('put', `router/updateMenu`, { data });
}; };
/** 系统管理-删除菜单 */ /** 菜单管理-删除菜单 */
export const deletedMenuByIds = (data?: any) => { export const deletedMenuByIds = (data?: any) => {
return http.request<BaseResult<any>>('delete', `router/deletedMenuByIds`, { data }); return http.request<BaseResult<any>>('delete', `router/deletedMenuByIds`, { data });
}; };

View File

@ -0,0 +1,76 @@
<script lang="ts" setup>
import { ref, watch } from 'vue';
const props = defineProps({
// ?
show: {
type: Boolean,
},
// ?
clickNodalFlag: {
type: Boolean,
default: false,
},
// ? esc
pressEscapeFlag: {
type: Boolean,
default: false,
},
// ?
cancelText: {
type: String as PropType<string>,
default: '返回',
},
// ?
confirmText: {
type: String as PropType<string>,
default: '确认',
},
});
const emits = defineEmits(['onCancel', 'onConfirm']);
const dialogVisible = ref(props.show);
/**
* * 返回时
*/
const onCancel = () => {
emits('onCancel', false);
};
/**
* * 当确认时
*/
const onConfirm = () => {
emits('onConfirm', false);
};
watch(
() => props.show,
() => {
dialogVisible.value = props.show;
},
);
</script>
<template>
<el-dialog v-model="dialogVisible" :close-on-click-modal="clickNodalFlag" :closeOnPressEscape="pressEscapeFlag" :onClose="onCancel" destroy-on-close draggable v-bind="$attrs">
<template #header>
<slot name="header" />
</template>
<slot name="default" />
<template #footer>
<slot name="dialogFooter">
<div class="dialog-footer">
<slot name="footer">
<el-button @click="onCancel">{{ cancelText }}</el-button>
<el-button type="primary" @click="onConfirm"> {{ confirmText }}</el-button>
</slot>
<slot name="footer-add" />
</div>
</slot>
</template>
</el-dialog>
</template>

View File

@ -0,0 +1,161 @@
<script lang="ts" setup>
import { IconJson } from '@/components/ReIcon/data';
import { cloneDeep, isAllEmpty } from '@pureadmin/utils';
import { computed, CSSProperties, ref, watch } from 'vue';
import Search from '@iconify-icons/ri/search-eye-line';
import { inputValue } from '@/components/ReIcon/src/hooks';
type ParameterCSSProperties = (item?: string) => CSSProperties | undefined;
const iconList = ref(IconJson);
const icon = ref();
const currentActiveType = ref('ep:');
//
const copyIconList = cloneDeep(iconList.value);
const totalPage = ref(0);
// 35
const pageSize = ref(35);
const currentPage = ref(1);
//
const filterValue = ref('');
const tabsList = [
{
label: 'Element Plus',
name: 'ep:',
},
{
label: 'Remix Icon',
name: 'ri:',
},
{
label: 'Font Awesome 5 Solid',
name: 'fa-solid:',
},
];
const pageList = computed(() => copyIconList[currentActiveType.value].filter(i => i.includes(filterValue.value)).slice((currentPage.value - 1) * pageSize.value, currentPage.value * pageSize.value));
const iconItemStyle = computed((): ParameterCSSProperties => {
return item => {
if (inputValue.value === currentActiveType.value + item) {
return {
borderColor: 'var(--el-color-primary)',
color: 'var(--el-color-primary)',
};
}
};
});
function setVal() {
currentActiveType.value = inputValue.value.substring(0, inputValue.value.indexOf(':') + 1);
icon.value = inputValue.value.substring(inputValue.value.indexOf(':') + 1);
}
function onAfterLeave() {
filterValue.value = '';
}
function handleClick({ props }) {
currentPage.value = 1;
currentActiveType.value = props.name;
}
function onChangeIcon(item) {
icon.value = item;
inputValue.value = currentActiveType.value + item;
}
function onCurrentChange(page) {
currentPage.value = page;
}
function onClear() {
icon.value = '';
inputValue.value = '';
}
function onBeforeEnter() {
if (isAllEmpty(icon.value)) return;
setVal();
//
const curIconIndex = copyIconList[currentActiveType.value].findIndex(i => i === icon.value);
currentPage.value = Math.ceil((curIconIndex + 1) / pageSize.value);
}
watch(
() => pageList.value,
() => (totalPage.value = copyIconList[currentActiveType.value].filter(i => i.includes(filterValue.value)).length),
{ immediate: true },
);
watch(
() => inputValue.value,
val => val && setVal(),
{ immediate: true },
);
watch(
() => filterValue.value,
() => (currentPage.value = 1),
);
</script>
<template>
<el-popover :popper-options="{ placement: 'auto' }" :width="350" popper-class="pure-popper" trigger="click" @before-enter="onBeforeEnter" @after-leave="onAfterLeave">
<template #reference>
<div class="w-[40px] h-[32px] cursor-pointer flex justify-center items-center">
<IconifyIconOffline v-if="!icon" :icon="Search" />
<IconifyIconOnline v-else :icon="inputValue" />
</div>
</template>
<el-input v-model="filterValue" class="px-2 pt-2" clearable placeholder="搜索图标" />
<el-tabs v-model="currentActiveType" @tab-click="handleClick">
<el-tab-pane v-for="(pane, index) in tabsList" :key="index" :label="pane.label" :name="pane.name">
<el-scrollbar height="220px">
<ul class="flex flex-wrap px-2 ml-2">
<li
v-for="(item, key) in pageList"
:key="key"
:style="iconItemStyle(item)"
:title="item"
class="icon-item p-2 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-[#e5e7eb]"
@click="onChangeIcon(item)"
>
<IconifyIconOnline :icon="currentActiveType + item" height="20px" width="20px" />
</li>
</ul>
<el-empty v-show="pageList.length === 0" :description="`${filterValue} 图标不存在`" :image-size="60" />
</el-scrollbar>
</el-tab-pane>
</el-tabs>
<div class="w-full h-9 flex items-center overflow-auto border-t border-[#e5e7eb]">
<el-pagination
:current-page="currentPage"
:page-size="pageSize"
:pager-count="5"
:total="totalPage"
background
class="flex-auto ml-2"
layout="pager"
size="small"
@current-change="onCurrentChange"
/>
<el-button bg class="justify-end mr-2 ml-2" size="small" text type="danger" @click="onClear"> 清空</el-button>
</div>
</el-popover>
</template>
<style lang="scss" scoped>
.icon-item {
&:hover {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
transition: all 0.4s;
transform: scaleX(1.05);
}
}
</style>

View File

@ -1,268 +1,62 @@
<script setup lang="ts"> <script lang="ts" setup>
import { IconJson } from "@/components/ReIcon/data"; import LocalSelect from '@/components/ReIcon/src/LocalSelect.vue';
import { cloneDeep, isAllEmpty } from "@pureadmin/utils"; import { inputValue } from '@/components/ReIcon/src/hooks';
import { ref, computed, CSSProperties, watch } from "vue";
import Search from "@iconify-icons/ri/search-eye-line";
type ParameterCSSProperties = (item?: string) => CSSProperties | undefined;
defineOptions({ defineOptions({
name: "IconSelect" name: 'IconSelect',
}); });
const inputValue = defineModel({ type: String });
const iconList = ref(IconJson);
const icon = ref();
const currentActiveType = ref("ep:");
//
const copyIconList = cloneDeep(iconList.value);
const totalPage = ref(0);
// 35
const pageSize = ref(35);
const currentPage = ref(1);
//
const filterValue = ref("");
const tabsList = [
{
label: "Element Plus",
name: "ep:"
},
{
label: "Remix Icon",
name: "ri:"
},
{
label: "Font Awesome 5 Solid",
name: "fa-solid:"
}
];
const pageList = computed(() =>
copyIconList[currentActiveType.value]
.filter(i => i.includes(filterValue.value))
.slice(
(currentPage.value - 1) * pageSize.value,
currentPage.value * pageSize.value
)
);
const iconItemStyle = computed((): ParameterCSSProperties => {
return item => {
if (inputValue.value === currentActiveType.value + item) {
return {
borderColor: "var(--el-color-primary)",
color: "var(--el-color-primary)"
};
}
};
});
function setVal() {
currentActiveType.value = inputValue.value.substring(
0,
inputValue.value.indexOf(":") + 1
);
icon.value = inputValue.value.substring(inputValue.value.indexOf(":") + 1);
}
function onBeforeEnter() {
if (isAllEmpty(icon.value)) return;
setVal();
//
const curIconIndex = copyIconList[currentActiveType.value].findIndex(
i => i === icon.value
);
currentPage.value = Math.ceil((curIconIndex + 1) / pageSize.value);
}
function onAfterLeave() {
filterValue.value = "";
}
function handleClick({ props }) {
currentPage.value = 1;
currentActiveType.value = props.name;
}
function onChangeIcon(item) {
icon.value = item;
inputValue.value = currentActiveType.value + item;
}
function onCurrentChange(page) {
currentPage.value = page;
}
function onClear() {
icon.value = "";
inputValue.value = "";
}
watch(
() => pageList.value,
() =>
(totalPage.value = copyIconList[currentActiveType.value].filter(i =>
i.includes(filterValue.value)
).length),
{ immediate: true }
);
watch(
() => inputValue.value,
val => val && setVal(),
{ immediate: true }
);
watch(
() => filterValue.value,
() => (currentPage.value = 1)
);
</script> </script>
<template> <template>
<div class="selector"> <div class="selector">
<el-input v-model="inputValue" disabled> <el-input v-model="inputValue" disabled>
<template #append> <template #append>
<el-popover <LocalSelect />
:width="350" </template>
trigger="click" </el-input>
popper-class="pure-popper" </div>
:popper-options="{
placement: 'auto'
}"
@before-enter="onBeforeEnter"
@after-leave="onAfterLeave"
>
<template #reference>
<div
class="w-[40px] h-[32px] cursor-pointer flex justify-center items-center"
>
<IconifyIconOffline v-if="!icon" :icon="Search" />
<IconifyIconOnline v-else :icon="inputValue" />
</div>
</template>
<el-input
v-model="filterValue"
class="px-2 pt-2"
placeholder="搜索图标"
clearable
/>
<el-tabs v-model="currentActiveType" @tab-click="handleClick">
<el-tab-pane
v-for="(pane, index) in tabsList"
:key="index"
:label="pane.label"
:name="pane.name"
>
<el-scrollbar height="220px">
<ul class="flex flex-wrap px-2 ml-2">
<li
v-for="(item, key) in pageList"
:key="key"
:title="item"
class="icon-item p-2 cursor-pointer mr-2 mt-1 flex justify-center items-center border border-[#e5e7eb]"
:style="iconItemStyle(item)"
@click="onChangeIcon(item)"
>
<IconifyIconOnline
:icon="currentActiveType + item"
width="20px"
height="20px"
/>
</li>
</ul>
<el-empty
v-show="pageList.length === 0"
:description="`${filterValue} 图标不存在`"
:image-size="60"
/>
</el-scrollbar>
</el-tab-pane>
</el-tabs>
<div
class="w-full h-9 flex items-center overflow-auto border-t border-[#e5e7eb]"
>
<el-pagination
class="flex-auto ml-2"
:total="totalPage"
:current-page="currentPage"
:page-size="pageSize"
:pager-count="5"
layout="pager"
background
size="small"
@current-change="onCurrentChange"
/>
<el-button
class="justify-end mr-2 ml-2"
type="danger"
size="small"
text
bg
@click="onClear"
>
清空
</el-button>
</div>
</el-popover>
</template>
</el-input>
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
.icon-item {
&:hover {
color: var(--el-color-primary);
border-color: var(--el-color-primary);
transition: all 0.4s;
transform: scaleX(1.05);
}
}
:deep(.el-tabs__nav-next) { :deep(.el-tabs__nav-next) {
font-size: 15px; font-size: 15px;
line-height: 32px; line-height: 32px;
box-shadow: -5px 0 5px -6px #ccc; box-shadow: -5px 0 5px -6px #ccc;
} }
:deep(.el-tabs__nav-prev) { :deep(.el-tabs__nav-prev) {
font-size: 15px; font-size: 15px;
line-height: 32px; line-height: 32px;
box-shadow: 5px 0 5px -6px #ccc; box-shadow: 5px 0 5px -6px #ccc;
} }
:deep(.el-input-group__append) { :deep(.el-input-group__append) {
padding: 0; padding: 0;
} }
:deep(.el-tabs__item) { :deep(.el-tabs__item) {
height: 30px; height: 30px;
font-size: 12px; font-size: 12px;
font-weight: normal; font-weight: normal;
line-height: 30px; line-height: 30px;
} }
:deep(.el-tabs__header), :deep(.el-tabs__header),
:deep(.el-tabs__nav-wrap) { :deep(.el-tabs__nav-wrap) {
position: static; position: static;
margin: 0; margin: 0;
box-shadow: 0 2px 5px rgb(0 0 0 / 6%); box-shadow: 0 2px 5px rgb(0 0 0 / 6%);
} }
:deep(.el-tabs__nav-wrap::after) { :deep(.el-tabs__nav-wrap::after) {
height: 0; height: 0;
} }
:deep(.el-tabs__nav-wrap) { :deep(.el-tabs__nav-wrap) {
padding: 0 24px; padding: 0 24px;
} }
:deep(.el-tabs__content) { :deep(.el-tabs__content) {
margin-top: 4px; margin-top: 4px;
} }
</style> </style>

View File

@ -1,6 +1,8 @@
import type { iconType } from "./types"; import type { iconType } from './types';
import { h, defineComponent, type Component } from "vue"; import { type Component, defineComponent, h, ref } from 'vue';
import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index"; import { FontIcon, IconifyIconOffline, IconifyIconOnline } from '../index';
export const inputValue = ref();
/** /**
* `iconfont` `svg` `iconify` * `iconfont` `svg` `iconify`
@ -10,52 +12,48 @@ import { IconifyIconOnline, IconifyIconOffline, FontIcon } from "../index";
* @returns Component * @returns Component
*/ */
export function useRenderIcon(icon: any, attrs?: iconType): Component { export function useRenderIcon(icon: any, attrs?: iconType): Component {
// iconfont // iconfont
const ifReg = /^IF-/; const ifReg = /^IF-/;
// typeof icon === "function" 属于SVG // typeof icon === "function" 属于SVG
if (ifReg.test(icon)) { if (ifReg.test(icon)) {
// iconfont // iconfont
const name = icon.split(ifReg)[1]; const name = icon.split(ifReg)[1];
const iconName = name.slice( const iconName = name.slice(0, name.indexOf(' ') == -1 ? name.length : name.indexOf(' '));
0, const iconType = name.slice(name.indexOf(' ') + 1, name.length);
name.indexOf(" ") == -1 ? name.length : name.indexOf(" ") return defineComponent({
); name: 'FontIcon',
const iconType = name.slice(name.indexOf(" ") + 1, name.length); render() {
return defineComponent({ return h(FontIcon, {
name: "FontIcon", icon: iconName,
render() { iconType,
return h(FontIcon, { ...attrs,
icon: iconName, });
iconType, },
...attrs });
}); } else if (typeof icon === 'function' || typeof icon?.render === 'function') {
} // svg
}); return attrs ? h(icon, { ...attrs }) : icon;
} else if (typeof icon === "function" || typeof icon?.render === "function") { } else if (typeof icon === 'object') {
// svg return defineComponent({
return attrs ? h(icon, { ...attrs }) : icon; name: 'OfflineIcon',
} else if (typeof icon === "object") { render() {
return defineComponent({ return h(IconifyIconOffline, {
name: "OfflineIcon", icon: icon,
render() { ...attrs,
return h(IconifyIconOffline, { });
icon: icon, },
...attrs });
}); } else {
} // 通过是否存在 : 符号来判断是在线还是本地图标,存在即是在线图标,反之
}); return defineComponent({
} else { name: 'Icon',
// 通过是否存在 : 符号来判断是在线还是本地图标,存在即是在线图标,反之 render() {
return defineComponent({ const IconifyIcon = icon && icon.includes(':') ? IconifyIconOnline : IconifyIconOffline;
name: "Icon", return h(IconifyIcon, {
render() { icon: icon,
const IconifyIcon = ...attrs,
icon && icon.includes(":") ? IconifyIconOnline : IconifyIconOffline; });
return h(IconifyIcon, { },
icon: icon, });
...attrs }
});
}
});
}
} }

View File

@ -0,0 +1,12 @@
<script lang="ts" setup>
defineProps({
status: {
type: Boolean,
},
});
</script>
<template>
<el-tag v-show="status" effect="dark" size="large" type="success"></el-tag>
<el-tag v-show="!status" effect="dark" size="large" type="danger"></el-tag>
</template>

View File

@ -1,95 +1,82 @@
<script lang="ts" setup> <script lang="ts" setup>
import { import { getCurrentInstance, nextTick, onMounted, PropType, ref, unref, watch } from 'vue';
getCurrentInstance, import { rendTipProps } from '@/components/TableBar/utils/tableConfig';
nextTick, import { cellHeaderStyle, getDropdownItemStyle, iconClass, topClass } from '@/components/TableBar/utils/tableStyle';
onMounted, import PureTable from '@pureadmin/table';
PropType, import { useRoute } from 'vue-router';
ref, import { useI18n } from 'vue-i18n';
unref, import RefreshIcon from '@/assets/table-bar/refresh.svg?component';
watch import CollapseIcon from '@/assets/table-bar/collapse.svg?component';
} from "vue"; import SettingIcon from '@/assets/table-bar/settings.svg?component';
import { rendTipProps } from "@/components/TableBar/utils/tableConfig"; import { cloneDeep, getKeyList, isBoolean, isFunction } from '@pureadmin/utils';
import { import DragIcon from '@/assets/table-bar/drag.svg?component';
cellHeaderStyle, import Sortable from 'sortablejs';
getDropdownItemStyle, import { $t } from '@/plugins/i18n';
iconClass, import { DeleteFilled, EditPen } from '@element-plus/icons-vue';
topClass import { useRenderIcon } from '@/components/CommonIcon/src/hooks';
} from "@/components/TableBar/utils/tableStyle"; import Refresh from '@iconify-icons/ep/refresh';
import PureTable from "@pureadmin/table"; import { FormInstance } from 'element-plus';
import { useRoute } from "vue-router";
import { useI18n } from "vue-i18n";
import RefreshIcon from "@/assets/table-bar/refresh.svg?component";
import CollapseIcon from "@/assets/table-bar/collapse.svg?component";
import SettingIcon from "@/assets/table-bar/settings.svg?component";
import { cloneDeep, getKeyList, isBoolean, isFunction } from "@pureadmin/utils";
import DragIcon from "@/assets/table-bar/drag.svg?component";
import Sortable from "sortablejs";
import { $t } from "@/plugins/i18n";
import { DeleteFilled, EditPen } from "@element-plus/icons-vue";
import { useRenderIcon } from "@/components/CommonIcon/src/hooks";
import Refresh from "@iconify-icons/ep/refresh";
import { FormInstance } from "element-plus";
// * // *
const props = defineProps({ const props = defineProps({
// //
dataList: { type: Array<any>, default: [] }, dataList: { type: Array<any>, default: [] },
// //
column: { type: Array as PropType<any>, default: () => [] }, column: { type: Array as PropType<any>, default: () => [] },
// //
loading: { type: Boolean, default: false }, loading: { type: Boolean, default: false },
// //
tableQueryFormVisible: { type: Boolean, default: true }, tableQueryFormVisible: { type: Boolean, default: true },
// small | default | large // small | default | large
size: { type: String as PropType<any>, default: "default" }, size: { type: String as PropType<any>, default: 'default' },
// //
pagination: { type: Object, default: Object }, pagination: { type: Object, default: Object },
// //
handleSelectionChange: { handleSelectionChange: {
type: Function as PropType<Function>, type: Function as PropType<Function>,
default: () => {} default: () => {},
}, },
// //
handleSizeChange: { handleSizeChange: {
type: Function as PropType<Function>, type: Function as PropType<Function>,
default: () => {} default: () => {},
}, },
// //
handleCurrentChange: { handleCurrentChange: {
type: Function as PropType<Function>, type: Function as PropType<Function>,
default: () => {} default: () => {},
}, },
// //
form: { form: {
type: Object as PropType<any>, type: Object as PropType<any>,
default: Object default: Object,
}, },
// key // key
tableKey: { tableKey: {
type: [String, Number] as PropType<string | number>, type: [String, Number] as PropType<string | number>,
default: "0" default: '0',
}, },
// //
tableTitle: { type: String, default: "" }, tableTitle: { type: String, default: '' },
// //
tableEdit: { tableEdit: {
type: Function as PropType<Function>, type: Function as PropType<Function>,
default: () => {} default: () => {},
}, // }, //
tableDelete: { tableDelete: {
type: Function as PropType<Function>, type: Function as PropType<Function>,
default: () => {} default: () => {},
}, },
// //
onReFresh: { onReFresh: {
type: Function as PropType<Function>, type: Function as PropType<Function>,
default: () => {} default: () => {},
}, },
onSearch: { type: Function as PropType<any> }, onSearch: { type: Function as PropType<any> },
model: { type: Object as PropType<any> } model: { type: Object as PropType<any> },
}); });
const emit = defineEmits(["changeColumn"]); const emit = defineEmits(['changeColumn']);
const { t, locale } = useI18n(); const { t, locale } = useI18n();
const route = useRoute(); const route = useRoute();
// //
@ -100,16 +87,10 @@ const isIndeterminate = ref(false);
// //
const dynamicColumns = ref(props.column); const dynamicColumns = ref(props.column);
// //
const filterColumns = cloneDeep(props.column).filter(column => const filterColumns = cloneDeep(props.column).filter(column => (isBoolean(column?.hide) ? !column.hide : !(isFunction(column?.hide) && column?.hide())));
isBoolean(column?.hide)
? !column.hide
: !(isFunction(column?.hide) && column?.hide())
);
// //
const checkedColumns = ref(getKeyList(cloneDeep(filterColumns), "label")); const checkedColumns = ref(getKeyList(cloneDeep(filterColumns), 'label'));
const checkColumnList = ref( const checkColumnList = ref(getKeyList(cloneDeep(dynamicColumns.value), 'label'));
getKeyList(cloneDeep(dynamicColumns.value), "label")
);
const instance = getCurrentInstance()!; const instance = getCurrentInstance()!;
const ruleFormRef = ref<FormInstance>(); const ruleFormRef = ref<FormInstance>();
@ -118,7 +99,7 @@ const ruleFormRef = ref<FormInstance>();
* @param value 修改样式大小 larger | default | small * @param value 修改样式大小 larger | default | small
*/ */
const handleTableSizeClick = (value: string) => { const handleTableSizeClick = (value: string) => {
size.value = value; size.value = value;
}; };
/** /**
@ -126,11 +107,9 @@ const handleTableSizeClick = (value: string) => {
* @param val 是否全部显示 * @param val 是否全部显示
*/ */
const handleCheckAllChange = (val: boolean) => { const handleCheckAllChange = (val: boolean) => {
checkedColumns.value = val ? checkColumnList.value : []; checkedColumns.value = val ? checkColumnList.value : [];
isIndeterminate.value = false; isIndeterminate.value = false;
dynamicColumns.value.map(column => dynamicColumns.value.map(column => (val ? (column.hide = false) : (column.hide = true)));
val ? (column.hide = false) : (column.hide = true)
);
}; };
/** /**
@ -138,11 +117,10 @@ const handleCheckAllChange = (val: boolean) => {
* @param value * @param value
*/ */
const handleCheckedColumnsChange = (value: string[]) => { const handleCheckedColumnsChange = (value: string[]) => {
checkedColumns.value = value; checkedColumns.value = value;
const checkedCount = value.length; const checkedCount = value.length;
checkAll.value = checkedCount === checkColumnList.value.length; checkAll.value = checkedCount === checkColumnList.value.length;
isIndeterminate.value = isIndeterminate.value = checkedCount > 0 && checkedCount < checkColumnList.value.length;
checkedCount > 0 && checkedCount < checkColumnList.value.length;
}; };
/** /**
@ -150,9 +128,7 @@ const handleCheckedColumnsChange = (value: string[]) => {
* @param label * @param label
*/ */
const handleCheckColumnListChange = (label: string) => { const handleCheckColumnListChange = (label: string) => {
dynamicColumns.value.filter(item => item.label === label)[0].hide = !( dynamicColumns.value.filter(item => item.label === label)[0].hide = !(event.target as any).checked;
event.target as any
).checked;
}; };
/** /**
@ -160,72 +136,67 @@ const handleCheckColumnListChange = (label: string) => {
* @param label * @param label
*/ */
const isFixedColumn = (label: string) => { const isFixedColumn = (label: string) => {
return dynamicColumns.value.filter(item => item.label === label)[0].fixed; return dynamicColumns.value.filter(item => item.label === label)[0].fixed;
}; };
/** /**
* * 重置自定义表格列字段 * * 重置自定义表格列字段
*/ */
const onReset = async () => { const onReset = async () => {
// //
const list = []; const list = [];
// true // true
checkAll.value = true; checkAll.value = true;
isIndeterminate.value = false; isIndeterminate.value = false;
// //
checkedColumns.value = getKeyList(cloneDeep(filterColumns), "label"); checkedColumns.value = getKeyList(cloneDeep(filterColumns), 'label');
// ? ref reactive // ? ref reactive
// ? Proxy 访使 // ? Proxy 访使
checkColumnList.value = []; checkColumnList.value = [];
await nextTick(() => { await nextTick(() => {
checkColumnList.value = getKeyList(filterColumns, "label"); checkColumnList.value = getKeyList(filterColumns, 'label');
}); });
// checkedColumns list // checkedColumns list
checkedColumns.value.forEach(item => { checkedColumns.value.forEach(item => {
dynamicColumns.value.forEach(column => { dynamicColumns.value.forEach(column => {
if (column.label == item) { if (column.label == item) {
list.push(column); list.push(column);
} }
}); });
}); });
emit("changeColumn", list); emit('changeColumn', list);
}; };
/** 列展示拖拽排序 */ /** 列展示拖拽排序 */
const rowDrop = (event: any) => { const rowDrop = (event: any) => {
nextTick(() => { nextTick(() => {
const wrapper: HTMLElement = ( const wrapper: HTMLElement = (instance?.proxy?.$refs[`GroupRef${unref(props.tableKey)}`] as any).$el.firstElementChild;
instance?.proxy?.$refs[`GroupRef${unref(props.tableKey)}`] as any Sortable.create(wrapper, {
).$el.firstElementChild; animation: 300,
Sortable.create(wrapper, { handle: '.drag-btn',
animation: 300, onEnd: ({ newIndex, oldIndex, item }) => {
handle: ".drag-btn", const targetThElem = item;
onEnd: ({ newIndex, oldIndex, item }) => { const wrapperElem = targetThElem.parentNode as HTMLElement;
const targetThElem = item; const oldColumn = dynamicColumns.value[oldIndex];
const wrapperElem = targetThElem.parentNode as HTMLElement; const newColumn = dynamicColumns.value[newIndex];
const oldColumn = dynamicColumns.value[oldIndex]; if (oldColumn?.fixed || newColumn?.fixed) {
const newColumn = dynamicColumns.value[newIndex]; // fixed
if (oldColumn?.fixed || newColumn?.fixed) { const oldThElem = wrapperElem.children[oldIndex] as HTMLElement;
// fixed if (newIndex > oldIndex) {
const oldThElem = wrapperElem.children[oldIndex] as HTMLElement; wrapperElem.insertBefore(targetThElem, oldThElem);
if (newIndex > oldIndex) { } else {
wrapperElem.insertBefore(targetThElem, oldThElem); wrapperElem.insertBefore(targetThElem, oldThElem ? oldThElem.nextElementSibling : oldThElem);
} else { }
wrapperElem.insertBefore( return;
targetThElem, }
oldThElem ? oldThElem.nextElementSibling : oldThElem const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0];
); dynamicColumns.value.splice(newIndex, 0, currentRow);
} emit('changeColumn', dynamicColumns.value);
return; },
} });
const currentRow = dynamicColumns.value.splice(oldIndex, 1)[0]; }).then();
dynamicColumns.value.splice(newIndex, 0, currentRow);
emit("changeColumn", dynamicColumns.value);
}
});
}).then();
}; };
/** /**
@ -233,234 +204,153 @@ const rowDrop = (event: any) => {
* @param formEl * @param formEl
*/ */
const resetForm = (formEl: FormInstance | undefined) => { const resetForm = (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.resetFields(); formEl.resetFields();
props.onSearch(); props.onSearch();
}; };
onMounted(() => { onMounted(() => {
watch([() => props.column], () => { watch([() => props.column], () => {
dynamicColumns.value = props.column; dynamicColumns.value = props.column;
}); });
}); });
</script> </script>
<template> <template>
<div class="main"> <div class="main">
<!-- 表单设置外加插槽 --> <!-- 表单设置外加插槽 -->
<el-form <el-form v-show="tableQueryFormVisible" ref="ruleFormRef" :inline="true" :model="model" class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto" @submit="onSearch">
v-show="tableQueryFormVisible" <slot name="tableForm" />
ref="ruleFormRef" <el-form-item>
:inline="true" <el-button :icon="useRenderIcon('ri:search-line')" :loading="loading" type="primary" @click="onSearch">
:model="model" {{ $t('buttons.search') }}
class="search-form bg-bg_color w-[99/100] pl-8 pt-[12px] overflow-auto" </el-button>
@submit="onSearch" <el-button :icon="useRenderIcon(Refresh)" @click="resetForm(ruleFormRef)">
> {{ $t('buttons.rest') }}
<slot name="tableForm" /> </el-button>
<el-form-item> </el-form-item>
<el-button </el-form>
:icon="useRenderIcon('ri:search-line')"
:loading="loading"
type="primary"
@click="onSearch"
>
{{ $t("buttons.search") }}
</el-button>
<el-button
:icon="useRenderIcon(Refresh)"
@click="resetForm(ruleFormRef)"
>
{{ $t("buttons.rest") }}</el-button
>
</el-form-item>
</el-form>
<!-- 表格头部设置 --> <!-- 表格头部设置 -->
<div class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color"> <div class="w-[99/100] mt-2 px-2 pb-2 bg-bg_color">
<div class="flex justify-between w-full h-[60px] p-4"> <div class="flex justify-between w-full h-[60px] p-4">
<!-- 自定义左边头部内容 --> <!-- 自定义左边头部内容 -->
<slot name="tableTitle"> <slot name="tableTitle">
<p class="font-bold truncate"> <p class="font-bold truncate">
{{ tableTitle ? tableTitle : t(route.meta.title) }} {{ tableTitle ? tableTitle : t(route.meta.title) }}
</p> </p>
</slot> </slot>
<!-- 自定义表格操作内容 --> <!-- 自定义表格操作内容 -->
<slot name="tableOperation"> <slot name="tableOperation">
<div class="flex items-center justify-around"> <div class="flex items-center justify-around">
<!-- 插槽内容 --> <!-- 插槽内容 -->
<div class="mr-4"> <div class="mr-4">
<slot name="tableButtons" /> <slot name="tableButtons" />
</div> </div>
<!-- 表格刷新按钮 --> <!-- 表格刷新按钮 -->
<RefreshIcon <RefreshIcon v-tippy="rendTipProps('刷新')" :class="`w-[16px] ${iconClass()}} ${loading ? 'animate-spin' : ''}`" @click="onReFresh" />
v-tippy="rendTipProps('刷新')" <el-divider direction="vertical" />
:class="`w-[16px] ${iconClass()}} ${loading ? 'animate-spin' : ''}`"
@click="onReFresh"
/>
<el-divider direction="vertical" />
<!-- 选择表格大小 --> <!-- 选择表格大小 -->
<el-dropdown trigger="click"> <el-dropdown trigger="click">
<CollapseIcon :class="`w-[16px] ${iconClass()}`" /> <CollapseIcon :class="`w-[16px] ${iconClass()}`" />
<template #dropdown> <template #dropdown>
<el-dropdown-menu class="translation"> <el-dropdown-menu class="translation">
<el-dropdown-item <el-dropdown-item :style="getDropdownItemStyle(size, 'large')" @click="handleTableSizeClick('large')">
:style="getDropdownItemStyle(size, 'large')" {{ $t('style.larger') }}
@click="handleTableSizeClick('large')" </el-dropdown-item>
> <el-dropdown-item :style="getDropdownItemStyle(size, 'default')" @click="handleTableSizeClick('default')">
{{ $t("style.larger") }} {{ t('style.default') }}
</el-dropdown-item> </el-dropdown-item>
<el-dropdown-item <el-dropdown-item :style="getDropdownItemStyle(size, 'small')" @click="handleTableSizeClick('small')">
:style="getDropdownItemStyle(size, 'default')" {{ t('style.small') }}
@click="handleTableSizeClick('default')" </el-dropdown-item>
> </el-dropdown-menu>
{{ t("style.default") }} </template>
</el-dropdown-item> </el-dropdown>
<el-dropdown-item <el-divider direction="vertical" />
:style="getDropdownItemStyle(size, 'small')"
@click="handleTableSizeClick('small')"
>
{{ t("style.small") }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-divider direction="vertical" />
<!-- 表格列设置 --> <!-- 表格列设置 -->
<el-popover <el-popover :popper-style="{ padding: 0 }" placement="bottom-start" trigger="click" width="200">
:popper-style="{ padding: 0 }" <template #reference>
placement="bottom-start" <SettingIcon v-tippy="rendTipProps('列设置')" :class="`w-[16px] ${iconClass()}`" />
trigger="click" </template>
width="200"
>
<template #reference>
<SettingIcon
v-tippy="rendTipProps('列设置')"
:class="`w-[16px] ${iconClass()}`"
/>
</template>
<div :class="topClass()"> <div :class="topClass()">
<el-checkbox <el-checkbox v-model="checkAll" :indeterminate="isIndeterminate" class="!-mr-1" label="列展示" @change="handleCheckAllChange" />
v-model="checkAll" <el-button link type="primary" @click="onReset">
:indeterminate="isIndeterminate" {{ t('buttons.rest') }}
class="!-mr-1" </el-button>
label="列展示" </div>
@change="handleCheckAllChange"
/>
<el-button link type="primary" @click="onReset">
{{ t("buttons.rest") }}</el-button
>
</div>
<div class="pt-[6px] pl-[11px]"> <div class="pt-[6px] pl-[11px]">
<el-scrollbar max-height="36vh"> <el-scrollbar max-height="36vh">
<el-checkbox-group <el-checkbox-group :ref="`GroupRef${unref(props.tableKey)}`" :modelValue="checkedColumns" @change="handleCheckedColumnsChange">
:ref="`GroupRef${unref(props.tableKey)}`" <el-space :alignment="'flex-start'" :size="0" direction="vertical">
:modelValue="checkedColumns" <div v-for="(item, index) in checkColumnList" :key="index" class="flex items-center">
@change="handleCheckedColumnsChange" <DragIcon :class="`drag-btn w-[16px] mr-2 ${isFixedColumn(item) ? '!cursor-no-drop' : '!cursor-grab'}`" @mouseenter.prevent="rowDrop" />
> <el-checkbox :key="index" :label="item" :value="item" @change="handleCheckColumnListChange(item)">
<el-space <span :title="item" class="inline-block w-[120px] truncate hover:text-text_color_primary">
:alignment="'flex-start'" {{ item }}
:size="0" </span>
direction="vertical" </el-checkbox>
> </div>
<div </el-space>
v-for="(item, index) in checkColumnList" </el-checkbox-group>
:key="index" </el-scrollbar>
class="flex items-center" </div>
> </el-popover>
<DragIcon </div>
:class="`drag-btn w-[16px] mr-2 ${isFixedColumn(item) ? '!cursor-no-drop' : '!cursor-grab'}`" </slot>
@mouseenter.prevent="rowDrop" </div>
/> <slot name="tableSelect" />
<el-checkbox <pure-table
:key="index" ref="tableRef"
:label="item" :adaptiveConfig="{ offsetBottom: 108 }"
:value="item" :columns="column"
@change="handleCheckColumnListChange(item)" :data="dataList"
> :header-cell-style="cellHeaderStyle"
<span :loading="loading"
:title="item" :pagination="pagination"
class="inline-block w-[120px] truncate hover:text-text_color_primary" :paginationSmall="size === 'small'"
> :size="size"
{{ item }} adaptive
</span> align-whole="center"
</el-checkbox> border
</div> row-key="id"
</el-space> stripe
</el-checkbox-group> table-layout="fixed"
</el-scrollbar> v-bind="$attrs"
</div> @page-size-change="handleSizeChange"
</el-popover> @page-current-change="handleCurrentChange"
</div> @selection-change="handleSelectionChange"
</slot> >
</div> <template v-for="item in column" :key="item.prop" v-slot:[item.slot]="scope" v-bind="item">
<slot name="tableSelect" /> <slot :name="item.slot" v-bind="scope" />
<pure-table <slot v-if="item.slot === 'operation'" name="operation">
ref="tableRef" <el-button :icon="EditPen" link type="warning" @click="tableEdit(scope)">修改 </el-button>
:adaptiveConfig="{ offsetBottom: 108 }" <el-popconfirm :title="t('table.popConfirmTitle')" @confirm="tableDelete(scope)">
:columns="column" <template #reference>
:data="dataList" <el-button :icon="DeleteFilled" link type="danger">删除 </el-button>
:header-cell-style="cellHeaderStyle" </template>
:loading="loading" </el-popconfirm>
:pagination="pagination" </slot>
:paginationSmall="size === 'small'" </template>
:size="size" </pure-table>
adaptive </div>
align-whole="center" </div>
border
row-key="id"
stripe
table-layout="fixed"
v-bind="$attrs"
@page-size-change="handleSizeChange"
@page-current-change="handleCurrentChange"
@selection-change="handleSelectionChange"
>
<template
v-for="item in column"
:key="item.prop"
v-slot:[item.slot]="scope"
v-bind="item"
>
<slot :name="item.slot" v-bind="scope" />
<slot v-if="item.slot === 'operation'" name="operation">
<el-button
:icon="EditPen"
link
type="warning"
@click="tableEdit(scope)"
>修改</el-button
>
<el-popconfirm
:title="t('table.popConfirmTitle')"
@confirm="tableDelete(scope)"
>
<template #reference>
<el-button :icon="DeleteFilled" link type="danger"
>删除</el-button
>
</template>
</el-popconfirm>
</slot>
</template>
</pure-table>
</div>
</div>
</template> </template>
<style lang="scss" scoped> <style lang="scss" scoped>
:deep(.el-dropdown-menu__item i) { :deep(.el-dropdown-menu__item i) {
margin: 0; margin: 0;
} }
.search-form { .search-form {
:deep(.el-form-item) { :deep(.el-form-item) {
margin-bottom: 12px; margin-bottom: 12px;
} }
} }
</style> </style>

View File

@ -169,19 +169,16 @@ export default defineComponent({
}; };
const isFixedColumn = (label: string) => { const isFixedColumn = (label: string) => {
return dynamicColumns.value.filter(item => $t(item.label) === $t(label))[0].fixed ? true : false; return !!dynamicColumns.value.filter(item => $t(item.label) === $t(label))[0].fixed;
}; };
const rendTippyProps = (content: string) => { const rendTippyProps = (content: string) => ({
// https://vue-tippy.netlify.app/props content,
return { offset: [0, 18],
content, duration: [300, 0],
offset: [0, 18], followCursor: true,
duration: [300, 0], hideOnClick: 'toggle',
followCursor: true, });
hideOnClick: 'toggle',
};
};
const reference = { const reference = {
reference: () => <SettingIcon class={['w-[16px]', iconClass.value]} v-tippy={rendTippyProps('列设置')} />, reference: () => <SettingIcon class={['w-[16px]', iconClass.value]} v-tippy={rendTippyProps('列设置')} />,
@ -198,9 +195,7 @@ export default defineComponent({
<> <>
<ExpandIcon <ExpandIcon
class={['w-[16px]', iconClass.value]} class={['w-[16px]', iconClass.value]}
style={{ style={{ transform: isExpandAll.value ? 'none' : 'rotate(-90deg)' }}
transform: isExpandAll.value ? 'none' : 'rotate(-90deg)',
}}
v-tippy={rendTippyProps(isExpandAll.value ? '折叠' : '展开')} v-tippy={rendTippyProps(isExpandAll.value ? '折叠' : '展开')}
onClick={() => onExpand()} onClick={() => onExpand()}
/> />
@ -245,12 +240,7 @@ export default defineComponent({
</el-popover> </el-popover>
<el-divider direction='vertical' /> <el-divider direction='vertical' />
<iconifyIconOffline <iconifyIconOffline class={['w-[16px]', iconClass.value]} icon={isFullscreen.value ? ExitFullscreen : Fullscreen} v-tippy={isFullscreen.value ? '退出全屏' : '全屏'} onClick={onFullscreen} />
class={['w-[16px]', iconClass.value]}
icon={isFullscreen.value ? ExitFullscreen : Fullscreen}
v-tippy={isFullscreen.value ? '退出全屏' : '全屏'}
onClick={() => onFullscreen()}
/>
</div> </div>
</div> </div>
{slots.default({ {slots.default({

28
src/enums/baseConstant.ts Normal file
View File

@ -0,0 +1,28 @@
/**
* *
*/
export const isDefaultOptions = [
{ value: true, label: '是' },
{ value: false, label: '否' },
];
/**
* *
*/
export const isDefaultVisibleOptions = [
{ value: true, label: '显示' },
{ value: false, label: '不显示' },
];
/**
* *
*/
export const sexConstant = [
{ value: 1, label: '男' },
{ value: 0, label: '女' },
];
/**
* *
*/
export const pageSizes: number[] = [15, 30, 50, 100, 150, 200, 300];

View File

@ -1,6 +1,7 @@
// import { fetchGetI18n } from '@/api/mock/i18n';
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import { fetchGetI18n } from '@/api/v1/i18n'; import { fetchAddI18n, fetchDeleteI18n, fetchGetI18n, fetchGetI18nList, fetchUpdateI18n } from '@/api/v1/i18n';
import { pageSizes } from '@/enums/baseConstant';
import { storeMessage } from '@/utils/message';
export const userI18nStore = defineStore('i18nStore', { export const userI18nStore = defineStore('i18nStore', {
persist: true, persist: true,
@ -8,6 +9,24 @@ export const userI18nStore = defineStore('i18nStore', {
return { return {
// ? 多语言内容 // ? 多语言内容
i18n: {}, i18n: {},
// 多语言列表
i18nDataList: [],
// 多语言类型
i18nTypeList: [],
isAddShown: false,
// ? 分页查询结果
pagination: {
currentPage: 1,
pageSize: 150,
total: 100,
pageSizes,
},
// 加载
loading: false,
// 添加弹窗
addDialogVisible: false,
// 更新弹窗
updateDialogVisible: false,
}; };
}, },
getters: {}, getters: {},
@ -29,5 +48,36 @@ export const userI18nStore = defineStore('i18nStore', {
this.i18n = data; this.i18n = data;
} }
}, },
/**
* *
*/
async getI18nMangeList(data: any) {
const result = await fetchGetI18nList(data);
return storeMessage(result);
},
/**
* *
*/
async addI18n(data: any) {
const result = await fetchAddI18n(data);
return storeMessage(result);
},
/**
* *
*/
async updateI18n(data: any) {
const result = await fetchUpdateI18n(data);
return storeMessage(result);
},
/**
* *
*/
async deleteI18n(data: any) {
const result = await fetchDeleteI18n(data);
return storeMessage(result);
},
}, },
}); });

View File

@ -0,0 +1,62 @@
import { defineStore } from 'pinia';
import { fetchAddI18nType, fetchDeleteI18nType, fetchGetI18nTypeList, fetchUpdateI18nType } from '@/api/v1/i18n';
import { pageSizes } from '@/enums/baseConstant';
import { storeMessage } from '@/utils/message';
export const userI18nTypeStore = defineStore('i18nTypeStore', {
persist: true,
state() {
return {
// 多语言列表
datalist: [],
isAddShown: false,
// ? 分页查询结果
pagination: {
currentPage: 1,
pageSize: 150,
total: 100,
pageSizes,
},
// 加载
loading: false,
// 添加弹窗
addDialogVisible: false,
// 更新弹窗
updateDialogVisible: false,
};
},
getters: {},
actions: {
/**
* *
*/
async getI18nTypeList() {
const result = await fetchGetI18nTypeList();
return storeMessage(result);
},
/**
* *
*/
async addI18nType(data: any) {
const result = await fetchAddI18nType(data);
return storeMessage(result);
},
/**
* *
*/
async updateI18nType(data: any) {
const result = await fetchUpdateI18nType(data);
return storeMessage(result);
},
/**
* *
*/
async deleteI18nType(data: any) {
const result = await fetchDeleteI18nType(data);
return storeMessage(result);
},
},
});

View File

@ -0,0 +1,23 @@
import { defineStore } from 'pinia';
import { getMenuIconList } from '@/api/v1/system';
export const userMenuIconStore = defineStore('menuIconStore', {
state() {
return {
menuIconList: [],
menuIconPage: { page: 1, limit: 30, total: 0 },
};
},
getters: {},
actions: {
/**
* *
*/
async getMenuIconList() {
const result = await getMenuIconList(this.menuIconPage);
if (result.code === 200) {
this.menuIconList = result.data.list;
}
},
},
});

View File

@ -1,6 +1,6 @@
import type { VNode } from 'vue'; import type { VNode } from 'vue';
import { isFunction } from '@pureadmin/utils'; import { isFunction } from '@pureadmin/utils';
import { ElMessage, type MessageHandler } from 'element-plus'; import { ElMessage, ElMessageBox, type MessageHandler } from 'element-plus';
import type { BaseResult } from '@/api/service/types'; import type { BaseResult } from '@/api/service/types';
type messageStyle = 'el' | 'antd'; type messageStyle = 'el' | 'antd';
@ -31,6 +31,15 @@ interface MessageParams {
onClose?: Function | null; onClose?: Function | null;
} }
// 消息盒
interface MessageBox {
message: string | undefined;
title: string | undefined;
confirmMessage: any;
cancelMessage: any;
showMessage: boolean;
}
/** 用法非常简单,参考 src/views/components/message/index.vue 文件 */ /** 用法非常简单,参考 src/views/components/message/index.vue 文件 */
/** /**
@ -91,3 +100,34 @@ export const storeMessage = (result: BaseResult<any>) => {
message(result.message, { type: 'success' }); message(result.message, { type: 'success' });
return true; return true;
}; };
const defaultBoxOption: MessageBox = {
showMessage: false,
message: '',
title: '',
confirmMessage: undefined,
cancelMessage: undefined,
};
/**
*
* @param type
* @param option
*/
export const messageBox = async (option: MessageBox = defaultBoxOption, type: any = 'warning') => {
return ElMessageBox.confirm(option.message, option.title, {
confirmButtonText: '确认',
cancelButtonText: '返回',
type,
draggable: true,
overflow: true,
})
.then(() => {
option.showMessage && ElMessage({ type: 'success', message: option.confirmMessage });
return true;
})
.catch(() => {
ElMessage({ type: 'warning', message: option.cancelMessage });
return false;
});
};

View File

@ -0,0 +1,61 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import LanguageAdd from '@/views/i18n/i18n-setting/language-add.vue';
import LanguageUpdate from '@/views/i18n/i18n-setting/language-update.vue';
import { userI18nStore } from '@/store/i18n/i18n';
import { addDialogVisible, getI18nDataList, onDelete, updateDialogVisible } from '@/views/i18n/i18n-setting/utils/hook';
import { useRenderIcon } from '@/components/ReIcon/src/hooks';
import AddFill from '@iconify-icons/ri/add-circle-line';
import EditPen from '@iconify-icons/ep/edit-pen';
import { $t } from '@/plugins/i18n';
import Delete from '@iconify-icons/ep/delete';
import PureTableBar from '@/components/TableBar/src/bar';
import PureTable from '@pureadmin/table';
import { columns } from '@/views/i18n/i18n-setting/utils/columns';
const tableRef = ref();
const i18nStore = userI18nStore();
onMounted(() => {
getI18nDataList();
});
</script>
<template>
<div class="main">
<language-add v-if="addDialogVisible" />
<language-update v-if="updateDialogVisible" />
<PureTableBar :columns="columns" :tableRef="tableRef?.getTableRef()" title="多语言管理" @fullscreen="tableRef.setAdaptive()" @refresh="getI18nDataList">
<template #buttons>
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="addDialogVisible = true"> 添加多语言</el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
<pure-table
ref="tableRef"
:adaptiveConfig="{ offsetBottom: 45 }"
:columns="dynamicColumns"
:data="i18nStore.i18nDataList"
:header-cell-style="{ background: 'var(--el-fill-color-light)', color: 'var(--el-text-color-primary)' }"
:loading="i18nStore.loading"
:size="size"
adaptive
align-whole="center"
row-key="id"
showOverflowTooltip
table-layout="auto"
>
<template #operation="{ row }">
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary"> 修改 </el-button>
<el-button v-show="row.menuType !== 3" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary"> 新增 </el-button>
<el-popconfirm :title="`是否确认删除菜单名称为${$t(row.title)}的这条数据${row?.children?.length > 0 ? '注意下级菜单也会一并删除,请谨慎操作' : ''}`" @confirm="onDelete(row)">
<template #reference>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary"> 删除 </el-button>
</template>
</el-popconfirm>
</template>
</pure-table>
</template>
</PureTableBar>
</div>
</template>

View File

@ -0,0 +1,67 @@
<script lang="ts" setup>
import LanguageDialog from '@/views/i18n/i18n-setting/language-dialog.vue';
import { reactive, ref } from 'vue';
import { FormInstance } from 'element-plus';
import { userI18nStore } from '@/store/i18n/i18n';
import { addDialogVisible } from '@/views/i18n/i18n-setting/utils/hook';
const i18nStore = userI18nStore();
const dialogRef = ref();
const form = reactive({
languageId: '',
keyName: '',
translate: '',
parentId: '',
});
/**
* * 关闭弹窗
*/
const onClose = () => {
addDialogVisible.value = false;
};
/**
* * 添加内容
*/
const onSubmit = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async valid => {
if (valid) {
//
const result = await i18nStore.addI18n(form);
if (!result) return false;
//
addDialogVisible.value = false;
await i18nStore.getI18nMangeList();
}
});
};
/**
* * 继续添加内容
*/
const onAddContinue = (value: any) => {
const formEl: FormInstance | undefined = value.ruleFormRef;
if (!formEl) return;
formEl.validate(async valid => {
if (valid) {
//
const result = await i18nStore.addI18n(form);
if (!result) return false;
//
await i18nStore.getI18nMangeList();
}
});
};
</script>
<template>
<language-dialog ref="dialogRef" :form="form" :on-close="onClose" :on-submit="onSubmit" :visible="addDialogVisible" title="添加多语言内容">
<template #footer>
<el-button type="success" @click="onAddContinue(dialogRef)">继续添加</el-button>
</template>
</language-dialog>
</template>

View File

@ -1,10 +1,9 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { ref } from 'vue';
import { FormInstance } from 'element-plus'; import { FormInstance } from 'element-plus';
import { useI18nTypeStore } from '@/store/i18n/i18nType'; import SimpleDialog from '@/components/BaseDialog/SimpleDialog.vue';
import SimpleDialog from '@/components/Dialog/SimpleDialog.vue';
import { userI18nStore } from '@/store/i18n/i18n'; import { userI18nStore } from '@/store/i18n/i18n';
import { rules } from '@/views/i18n/language-setting/utils/columns'; import { rules } from '@/views/i18n/i18n-setting/utils/columns';
defineProps({ defineProps({
// //
@ -33,7 +32,6 @@ defineProps({
}); });
const ruleFormRef = ref<FormInstance>(); const ruleFormRef = ref<FormInstance>();
const i18nTypeStore = useI18nTypeStore();
const i18nStore = userI18nStore(); const i18nStore = userI18nStore();
defineExpose({ defineExpose({
@ -44,15 +42,9 @@ defineExpose({
<template> <template>
<SimpleDialog :show="visible" :title="title" width="600" @onCancel="onClose" @onConfirm="onSubmit(ruleFormRef)"> <SimpleDialog :show="visible" :title="title" width="600" @onCancel="onClose" @onConfirm="onSubmit(ruleFormRef)">
<el-form ref="ruleFormRef" :model="form" :rules="rules" isDefault-icon label-position="left" label-width="135px"> <el-form ref="ruleFormRef" :model="form" :rules="rules" isDefault-icon label-position="left" label-width="135px">
<el-form-item label="选择添加语言分类" prop="parentId">
<el-select v-model="form.parentId!" clearable filterable placeholder="选择添加语言分类">
<el-option v-for="item in i18nStore.languageParentList" :key="item.id" :label="item.translate" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="选择添加语言分类" prop="languageId"> <el-form-item label="选择添加语言分类" prop="languageId">
<el-select v-model="form.languageId!" filterable placeholder="选择添加语言分类"> <el-select v-model="form.languageId!" filterable placeholder="选择添加语言分类">
<el-option v-for="item in i18nTypeStore.languageTypeMap" :key="item.id" :label="item.value" :value="item.id" /> <el-option v-for="item in i18nStore.i18nTypeList" :key="item.id" :label="item.value" :value="item.id" />
</el-select> </el-select>
</el-form-item> </el-form-item>

View File

@ -0,0 +1,37 @@
<script lang="ts" setup>
import LanguageDialog from '@/views/i18n/i18n-setting/language-dialog.vue';
import { FormInstance } from 'element-plus';
import { userI18nStore } from '@/store/i18n/i18n';
import { updateDialogVisible, updateForm } from '@/views/i18n/i18n-setting/utils/hook';
const i18nStore = userI18nStore();
/**
* * 关闭弹窗
*/
const onClose = () => {
updateDialogVisible.value = false;
};
/**
* * 添加内容
*/
const onSubmit = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async valid => {
if (valid) {
//
const result = await i18nStore.updateI18n(updateForm);
if (!result) return false;
//
updateDialogVisible.value = false;
await i18nStore.getI18nMangeList();
}
});
};
</script>
<template>
<language-dialog :form="updateForm" :on-close="onClose" :on-submit="onSubmit" :visible="updateDialogVisible" title="修改多语言内容" />
</template>

View File

@ -1,17 +1,16 @@
// 多语言表格列字段 // 多语言表格列字段
import { reactive } from 'vue'; import { reactive } from 'vue';
import type { FormRules } from 'element-plus'; import type { FormRules } from 'element-plus';
import { $t } from '@/plugins/i18n';
export const columns: TableColumnList = [ export const columns: TableColumnList = [
{ type: 'selection' }, // { type: 'selection' },
{ type: 'index', label: $t('table.tableNumber'), width: 100 }, // { type: 'index', label: 'table.tableNumber', width: 100 },
{ label: $t('i18n.keyName'), prop: 'keyName' }, { label: 'i18n.keyName', prop: 'keyName' },
{ label: $t('i18n.translate'), prop: 'translate' }, { label: 'i18n.translate', prop: 'translate' },
{ label: $t('i18n.languageSummary'), prop: 'languageSummary' }, { label: 'i18n.languageSummary', prop: 'languageSummary' },
{ label: $t('i18n.languageName'), prop: 'languageName' }, { label: 'i18n.languageName', prop: 'languageName' },
{ label: $t('i18n.parentKeyName'), prop: 'parentKeyName' }, { label: 'i18n.parentKeyName', prop: 'parentKeyName' },
{ label: $t('table.operation'), prop: 'operation', slot: 'operation' }, { label: 'table.operation', prop: 'operation', slot: 'operation' },
]; ];
// 添加多语言表单规则 // 添加多语言表单规则

View File

@ -0,0 +1,125 @@
import { reactive, ref } from 'vue';
import { userI18nStore } from '@/store/i18n/i18n';
import { messageBox } from '@/utils/message';
export const addDialogVisible = ref(false);
export const updateDialogVisible = ref(false);
const i18nStore = userI18nStore();
// 更新表单数据
export const updateForm = reactive({
id: '',
languageId: '',
keyName: '',
translate: '',
parentId: '',
});
// 彻底删除 id列表
const ids = ref<string[]>([]);
/**
* *
*/
export const getI18nDataList = async () => {
i18nStore.loading = true;
await i18nStore.getI18nMangeList();
i18nStore.loading = false;
};
/**
* *
*/
export const onCurrentPageChange = async (value: number) => {
i18nStore.pagination.currentPage = value;
await getI18nDataList();
};
/**
* *
* @param value
*/
export const onPageSizeChange = async (value: number) => {
i18nStore.pagination.pageSize = value;
await getI18nDataList();
};
/**
* *
* @param row
*/
export const onSelectionChange = (row: any) => {
ids.value = row.map((item: any) => item.id);
};
/**
* *
*
*/
export const onRowAdd = () => {
addDialogVisible.value = true;
};
/**
* *
*/
export const onRowDelete = async () => {
const isConfirm = await messageBox({
message: '是否确认批量删除(此操作不可逆)',
title: '删除警告',
showMessage: false,
confirmMessage: '删除成功',
cancelMessage: '取消删除',
});
if (isConfirm) {
const data = ids.value;
await i18nStore.deleteI18n(data);
await getI18nDataList();
}
};
/**
* *
*/
export const onRowDeleteWithChildren = async () => {
const isConfirm = await messageBox({
message: '是否确认批量删除(此操作不可逆)',
title: '删除警告',
showMessage: false,
confirmMessage: '删除成功',
cancelMessage: '取消删除',
});
if (isConfirm) {
const data = ids.value;
await i18nStore.deleteI18n(data);
await getI18nDataList();
}
};
/**
* *
*/
export const onUpdate = (row: any) => {
const data = row.row;
// 赋值内容
updateForm.id = data.id;
updateForm.languageId = data.parentKeyName;
updateForm.keyName = data.keyName;
updateForm.translate = data.translate;
data.parentId != 0 && (updateForm.parentId = data.parentId);
// 打开弹窗
updateDialogVisible.value = true;
};
/**
* *
* @param row
*/
export const onDelete = async (row: any) => {
const id = row.row.id;
const form = { id };
await i18nStore.deleteI18n(form);
await i18nStore.getI18nMangeList();
};

View File

@ -1,17 +1,18 @@
<script lang="ts" setup> <script lang="ts" setup>
import SimpleDialog from '@/components/Dialog/SimpleDialog.vue'; import SimpleDialog from '@/components/BaseDialog/SimpleDialog.vue';
import { useI18nTypeStore } from '@/store/i18n/i18nType';
import { reactive, ref } from 'vue'; import { reactive, ref } from 'vue';
import { ElMessage, FormInstance } from 'element-plus'; import { ElMessage, FormInstance } from 'element-plus';
import { addRules } from '@/views/i18n/language-type-setting/utils/rules'; import { addRules } from '@/views/i18n/i18n-type-setting/utils/rules';
import { isDefaultOptions } from '@/enums/baseConstant'; import { isDefaultOptions } from '@/enums/baseConstant';
import { $t } from '@/plugins/i18n';
import { userI18nStore } from '@/store/i18n/i18n';
const formRef = ref<FormInstance>(); const formRef = ref<FormInstance>();
const i18nTypeStore = useI18nTypeStore(); const i18nStore = userI18nStore();
const form = reactive({ const form = reactive({
languageName: '', languageName: '',
summary: '', summary: '',
isDefalut: false, isDefault: false,
}); });
/** /**
@ -22,9 +23,9 @@ const submitForm = (formEl: FormInstance | undefined) => {
if (!formEl) return; if (!formEl) return;
formEl.validate(async valid => { formEl.validate(async valid => {
if (valid) { if (valid) {
await i18nTypeStore.addLanguageType(form); await i18nStore.addI18nType(form);
await i18nTypeStore.getLanguageType(); await i18nStore.getI18nTypeList();
i18nTypeStore.isAddShown = false; i18nStore.isAddShown = false;
} else { } else {
ElMessage.warning('请填写必填项'); ElMessage.warning('请填写必填项');
} }
@ -44,12 +45,12 @@ const resetForm = (formEl: FormInstance | undefined) => {
* * 关闭弹窗 * * 关闭弹窗
*/ */
const onCancel = (value: boolean) => { const onCancel = (value: boolean) => {
i18nTypeStore.isAddShown = value; i18nStore.isAddShown = value;
}; };
</script> </script>
<template> <template>
<SimpleDialog :show="i18nTypeStore.isAddShown" width="600" @on-cancel="onCancel"> <SimpleDialog :show="i18nStore.isAddShown" width="600" @on-cancel="onCancel">
<template #header> <template #header>
<h1>添加多语言种类</h1> <h1>添加多语言种类</h1>
</template> </template>
@ -62,7 +63,7 @@ const onCancel = (value: boolean) => {
<el-input v-model="form.summary" autocomplete="off" type="text" /> <el-input v-model="form.summary" autocomplete="off" type="text" />
</el-form-item> </el-form-item>
<el-form-item label="是否为默认语言" prop="isDefault"> <el-form-item label="是否为默认语言" prop="isDefault">
<el-select v-model="form.isDefalut" placeholder="选择是否为默认"> <el-select v-model="form.isDefault" placeholder="选择是否为默认">
<el-option v-for="(item, index) in isDefaultOptions" :key="index" :label="item.label" :value="item.value" /> <el-option v-for="(item, index) in isDefaultOptions" :key="index" :label="item.label" :value="item.value" />
</el-select> </el-select>
</el-form-item> </el-form-item>

View File

@ -0,0 +1,97 @@
<script lang="ts" setup>
import { onMounted, ref } from 'vue';
import { columns } from '@/views/i18n/i18n-type-setting/utils/columns';
import { userI18nStore } from '@/store/i18n/i18n';
import { messageBox } from '@/utils/message';
import { addDialogVisible, getI18nDataList, onDelete } from '@/views/i18n/i18n-setting/utils/hook';
import PureTableBar from '@/components/TableBar/src/bar';
import { useRenderIcon } from '@/components/ReIcon/src/hooks';
import AddFill from '@iconify-icons/ri/add-circle-line';
import EditPen from '@iconify-icons/ep/edit-pen';
import { $t } from '@/plugins/i18n';
import Delete from '@iconify-icons/ep/delete';
import PureTable from '@pureadmin/table';
import AddI18nType from '@/views/i18n/i18n-type-setting/add-i18n-type.vue';
const i18nStore = userI18nStore();
const tableRef = ref();
/**
* * 获取多语言类型
*/
const getI18nType = () => {
i18nStore.getI18nTypeList();
};
/**
* * 表格的列添加语言类型
*/
const onColumnAdd = () => {
i18nStore.isAddShown = true;
};
/**
* * 删除多语言类容
*/
const onColumnDelete = async (row: any, isPhysicalDelete: boolean) => {
const id = row.id;
const title = isPhysicalDelete ? '删除不可逆!' : '确认删除吗?';
messageBox({
message: `删除这条 【${row.summary}`,
title,
showMessage: false,
confirmMessage: undefined,
cancelMessage: '取消删除',
})
.then(result => {
if (result) {
i18nStore.deleteI18nType(id);
}
return result;
})
.then(result => result && getI18nType());
};
onMounted(() => {
getI18nType();
});
</script>
<template>
<div class="main">
<!-- 添加多语言种类 -->
<add-I18n-type />
<PureTableBar :columns="columns" :tableRef="tableRef?.getTableRef()" title="多语言类型管理" @fullscreen="tableRef.setAdaptive()" @refresh="getI18nDataList">
<template #buttons>
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="addDialogVisible = true"> 添加多语言 </el-button>
</template>
<template v-slot="{ size, dynamicColumns }">
<pure-table
ref="tableRef"
:adaptiveConfig="{ offsetBottom: 45 }"
:columns="dynamicColumns"
:data="i18nStore.i18nDataList"
:header-cell-style="{ background: 'var(--el-fill-color-light)', color: 'var(--el-text-color-primary)' }"
:loading="i18nStore.loading"
:size="size"
adaptive
align-whole="center"
row-key="id"
showOverflowTooltip
table-layout="auto"
>
<template #operation="{ row }">
<el-button :icon="useRenderIcon(EditPen)" :size="size" class="reset-margin" link type="primary"> 修改 </el-button>
<el-button v-show="row.menuType !== 3" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary"> 新增 </el-button>
<el-popconfirm :title="`是否确认删除菜单名称为${$t(row.title)}的这条数据${row?.children?.length > 0 ? '注意下级菜单也会一并删除,请谨慎操作' : ''}`" @confirm="onDelete(row)">
<template #reference>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary"> 删除 </el-button>
</template>
</el-popconfirm>
</template>
</pure-table>
</template>
</PureTableBar>
</div>
</template>

View File

@ -1,37 +1,36 @@
import { ref } from 'vue'; import { ref } from 'vue';
import { delObjectProperty } from '@pureadmin/utils'; import { delObjectProperty } from '@pureadmin/utils';
import { $t } from '@/plugins/i18n';
import { useI18nTypeStore } from '@/store/i18n/i18nType';
import { isDefaultOptions } from '@/enums/baseConstant'; import { isDefaultOptions } from '@/enums/baseConstant';
import TableIsDefaultTag from '@/components/Table/TableIsDefaultTag.vue'; import TableIsDefaultTag from '@/components/TableBar/src/TableIsDefaultTag.vue';
import { userI18nTypeStore } from '@/store/i18n/i18nType';
const i18nTypeStore = useI18nTypeStore(); const i18nTypeStore = userI18nTypeStore();
export const editMap = ref({}); export const editMap = ref({});
export const columns: TableColumnList = [ export const columns: TableColumnList = [
{ type: 'index', label: '序号', width: 100 }, // { type: 'index', label: '序号', width: 100 },
{ {
label: $t('i18n.languageName'), label: 'i18n.languageName',
prop: 'languageName', prop: 'languageName',
cellRenderer({ row, index }) { cellRenderer({ row, index }) {
return <>{editMap.value[index]?.editable ? <el-input v-model={row.languageName} /> : <p>{row.languageName}</p>}</>; return <>{editMap.value[index]?.editable ? <el-input v-model={row.languageName} /> : <p>{row.languageName}</p>}</>;
}, },
}, },
{ {
label: $t('i18n.languageSummary'), label: 'i18n.languageSummary',
prop: 'summary', prop: 'summary',
cellRenderer({ row, index }) { cellRenderer({ row, index }) {
return <>{editMap.value[index]?.editable ? <el-input v-model={row.summary} /> : <p>{row.summary}</p>}</>; return <>{editMap.value[index]?.editable ? <el-input v-model={row.summary} /> : <p>{row.summary}</p>}</>;
}, },
}, },
{ {
label: $t('i18n.isDefault'), label: 'i18n.isDefault',
prop: 'isDefault', prop: 'isDefault',
cellRenderer({ row, index }) { cellRenderer({ row, index }) {
return ( return (
<> <>
{editMap.value[index]?.editable ? ( {editMap.value[index]?.editable ? (
<el-select placeholder={$t('table.chooseIsDefault')} v-model={row.isDefault}> <el-select placeholder={'table.chooseIsDefault'} v-model={row.isDefault}>
{isDefaultOptions.map(item => ( {isDefaultOptions.map(item => (
<el-option key={item.value} label={item.label} value={item.value} /> <el-option key={item.value} label={item.label} value={item.value} />
))} ))}
@ -44,7 +43,7 @@ export const columns: TableColumnList = [
}, },
}, },
// ? 表格操作 // ? 表格操作
{ label: $t('table.operation'), prop: 'op', slot: 'op', width: 160, fixed: 'right' }, { label: 'table.operation', prop: 'op', slot: 'op', width: 160, fixed: 'right' },
]; ];
/** /**
@ -62,9 +61,9 @@ export function onEdit(row: any, index: number) {
* @param index * @param index
*/ */
export async function onSave(row: any, index: number) { export async function onSave(row: any, index: number) {
await i18nTypeStore.updateLanguageType(row); await i18nTypeStore.updateI18nType(row);
editMap.value[index].editable = false; editMap.value[index].editable = false;
await i18nTypeStore.getLanguageType(); await i18nTypeStore.getI18nTypeList();
} }
/** /**
@ -73,5 +72,5 @@ export async function onSave(row: any, index: number) {
*/ */
export function onCancel(index: number) { export function onCancel(index: number) {
editMap.value[index].editable = false; editMap.value[index].editable = false;
i18nTypeStore.dataList[index] = delObjectProperty(editMap.value[index], 'editable'); i18nTypeStore.datalist[index] = delObjectProperty(editMap.value[index], 'editable');
} }

View File

@ -1,184 +0,0 @@
<script lang="ts" setup>
import TablePlusBar from '@/components/TableBar/src/TablePlusBar.vue';
import { DeleteFilled, Plus } from '@element-plus/icons-vue';
import { onMounted, reactive, ref } from 'vue';
import { columns } from '@/views/i18n/language-setting/utils/columns';
import { userI18nSettingStore } from '@/store/i18n/i18nSetting';
import { useI18nTypeStore } from '@/store/i18n/i18nType';
import LanguageAdd from '@/views/i18n/language-setting/language-add.vue';
import LanguageUpdate from '@/views/i18n/language-setting/language-update.vue';
import { I18nSettingsRow, I18nSettingsUpdateForm } from '@/types/store/system/i18n/i18n-setting';
import { messageBox } from '@/utils/message';
const i18nSettingStore = userI18nSettingStore();
const i18nTypeStore = useI18nTypeStore();
const column = ref(columns);
//
const updateForm = reactive<I18nSettingsUpdateForm>({
id: '',
languageId: '',
keyName: '',
translate: '',
parentId: '',
});
// id
const ids = ref<string[]>([]);
/**
* * 查询内容
*/
const getI18nDataList = async () => {
i18nSettingStore.loading = true;
await i18nSettingStore.getQueryI18nPage();
i18nSettingStore.loading = false;
};
/**
* * 当前页改变时
*/
const onCurrentPageChange = async (value: number) => {
i18nSettingStore.pagination.currentPage = value;
await getI18nDataList();
};
/**
* * 当分页发生变化
* @param value
*/
const onPageSizeChange = (value: number) => {
i18nSettingStore.pagination.pageSize = value;
getI18nDataList();
};
/**
* * 选择框点击的行
* @param row
*/
const onSelectionChange = (row: I18nSettingsRow[]) => {
ids.value = row.map(item => item.id);
};
/**
* * 行内容添加
* 打开添加弹窗
*/
const onRowAdd = () => {
i18nSettingStore.addDialogVisible = true;
};
/**
* * 批量彻底删除行
*/
const onRowDelete = async () => {
const isConfirm = await messageBox({
message: '是否确认批量删除(此操作不可逆)',
title: '删除警告',
showMessage: false,
confirmMessage: '删除成功',
cancelMessage: '取消删除',
});
if (isConfirm) {
const data = ids.value;
await i18nSettingStore.deleteI18nByIds(data);
await getI18nDataList();
}
};
/**
* * 批量彻底删除行
*/
const onRowDeleteWithChildren = async () => {
const isConfirm = await messageBox({
message: '是否确认批量删除(此操作不可逆)',
title: '删除警告',
showMessage: false,
confirmMessage: '删除成功',
cancelMessage: '取消删除',
});
if (isConfirm) {
const data = ids.value;
await i18nSettingStore.deleteI18nByIdsWithChildren(data);
await getI18nDataList();
}
};
/**
* * 当表格修改时
*/
const onUpdate = (row: any) => {
const data = row.row;
// keyid
const languageTypeMap = i18nTypeStore.languageTypeMap.filter(item => item.key == data.languageName);
//
updateForm.id = data.id;
updateForm.languageId = data.parentKeyName;
updateForm.keyName = data.keyName;
updateForm.translate = data.translate;
data.parentId != 0 && (updateForm.parentId = data.parentId);
updateForm.languageId = languageTypeMap[0].id;
//
i18nSettingStore.updateDialogVisible = true;
};
/**
* * 当删除时
* @param row
*/
const onDelete = async (row: any) => {
const id = row.row.id;
const form = { id };
await i18nSettingStore.deleteI18n(form);
await i18nSettingStore.getQueryI18nPage();
};
onMounted(() => {
getI18nDataList();
});
</script>
<template>
<div class="flex justify-between">
<language-add v-if="i18nSettingStore.addDialogVisible" />
<language-update v-if="i18nSettingStore.updateDialogVisible" :form="updateForm" />
<TablePlusBar
:column="column"
:data-list="i18nSettingStore.dataList"
:handle-current-change="onCurrentPageChange"
:handle-selection-change="onSelectionChange"
:handle-size-change="onPageSizeChange"
:loading="i18nSettingStore.loading"
:model="i18nSettingStore.form"
:on-re-fresh="getI18nDataList"
:on-search="getI18nDataList"
:pagination="i18nSettingStore.pagination"
:table-delete="onDelete"
:table-edit="onUpdate"
class="flex-1"
@change-column="args => (column = args)"
>
<template #tableForm>
<el-form-item label="多语言key" prop="keyName">
<el-input v-model="i18nSettingStore.form.keyName" class="!w-[150px]" clearable placeholder="输入多语言key" />
</el-form-item>
<el-form-item label="翻译" prop="translate">
<el-input v-model="i18nSettingStore.form.translate" class="!w-[150px]" clearable placeholder="输入多语言翻译" />
</el-form-item>
<el-form-item label="语言名称" prop="languageName" style="width: 240px">
<el-select v-model="i18nSettingStore.form.languageName" filterable placeholder="选择语言名称">
<el-option v-for="item in i18nTypeStore.languageTypeMap" :key="item.id" :label="item.value" :value="item.key" />
</el-select>
</el-form-item>
</template>
<template #tableButtons>
<el-button :icon="Plus" type="primary" @click="onRowAdd">{{ $t('buttons.add') }}</el-button>
<el-button v-show="ids.length > 0" :icon="DeleteFilled" type="danger" @click="onRowDelete">
{{ $t('buttons.deleteBatches') }}
</el-button>
<el-button v-show="ids.length > 0" :icon="DeleteFilled" type="danger" @click="onRowDeleteWithChildren"> 彻底删除包含删除子级内容 </el-button>
</template>
</TablePlusBar>
</div>
</template>

View File

@ -1,89 +0,0 @@
<script lang="ts" setup>
import LanguageDialog from '@/views/i18n/language-setting/language-dialog.vue';
import { onMounted, reactive, ref } from 'vue';
import { ElMessage, FormInstance } from 'element-plus';
import { userI18nSettingStore } from '@/store/i18n/i18nSetting';
import { storeToRefs } from 'pinia';
import { I18nSettingsAddForm } from '@/types/store/system/i18n/i18n-setting';
import { userI18nStore } from '@/store/i18n/i18n';
import { $t } from '@/plugins/i18n';
const i18nSettingStore = userI18nSettingStore();
const i18nStore = userI18nStore();
const { addDialogVisible } = storeToRefs(i18nSettingStore);
const dialogRef = ref();
const form = reactive<I18nSettingsAddForm>({
languageId: '',
keyName: '',
translate: '',
parentId: '',
});
/**
* * 获取多语言所有父级内容
*/
const getLanguageAllParentList = () => {
i18nStore.getLanguageAllParentList();
};
/**
* * 关闭弹窗
*/
const onClose = () => {
i18nSettingStore.addDialogVisible = false;
};
/**
* * 添加内容
*/
const onSubmit = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async valid => {
if (valid) {
//
const result = await i18nSettingStore.addLanguage(form);
if (!result) return false;
//
i18nSettingStore.addDialogVisible = false;
await i18nSettingStore.getQueryI18nPage();
} else {
ElMessage.closeAll();
ElMessage.warning($t('status.requiredFields'));
}
});
};
/**
* * 继续添加内容
*/
const onAddContinue = (value: any) => {
const formEl: FormInstance | undefined = value.ruleFormRef;
if (!formEl) return;
formEl.validate(async valid => {
if (valid) {
//
const result = await i18nSettingStore.addLanguage(form);
if (!result) return false;
//
await i18nSettingStore.getQueryI18nPage();
} else {
ElMessage.closeAll();
ElMessage.warning($t('status.requiredFields'));
}
});
};
onMounted(() => {
getLanguageAllParentList();
});
</script>
<template>
<language-dialog ref="dialogRef" :form="form" :on-close="onClose" :on-submit="onSubmit" :visible="addDialogVisible" title="添加多语言内容">
<template #footer>
<el-button type="success" @click="onAddContinue(dialogRef)">继续添加</el-button>
</template>
</language-dialog>
</template>

View File

@ -1,61 +0,0 @@
<script lang="ts" setup>
import { userI18nSettingStore } from '@/store/i18n/i18nSetting';
import { storeToRefs } from 'pinia';
import { onMounted } from 'vue';
import LanguageDialog from '@/views/i18n/language-setting/language-dialog.vue';
import { ElMessage, FormInstance } from 'element-plus';
import { I18nSettingsUpdateForm } from '@/types/store/system/i18n/i18n-setting';
import { userI18nStore } from '@/store/i18n/i18n';
import { $t } from '@/plugins/i18n';
const props = defineProps({
form: { type: Object as PropType<I18nSettingsUpdateForm> },
});
const i18nSettingStore = userI18nSettingStore();
const i18nStore = userI18nStore();
const { updateDialogVisible } = storeToRefs(i18nSettingStore);
/**
* * 获取多语言所有父级内容
*/
const getLanguageAllParentList = () => {
i18nStore.getLanguageAllParentList();
};
/**
* * 关闭弹窗
*/
const onClose = () => {
i18nSettingStore.updateDialogVisible = false;
};
/**
* * 添加内容
*/
const onSubmit = (formEl: FormInstance | undefined) => {
if (!formEl) return;
formEl.validate(async valid => {
if (valid) {
//
const result = await i18nSettingStore.updateLanguage(props.form);
if (!result) return false;
//
i18nSettingStore.updateDialogVisible = false;
await i18nSettingStore.getQueryI18nPage();
} else {
ElMessage.closeAll();
ElMessage.warning($t('status.requiredFields'));
}
});
};
onMounted(() => {
getLanguageAllParentList();
});
</script>
<template>
<language-dialog :form="form" :on-close="onClose" :on-submit="onSubmit" :visible="updateDialogVisible" title="修改多语言内容" />
</template>

View File

@ -1,101 +0,0 @@
<script lang="ts" setup>
import TablePlusBar from '@/components/TableBar/src/TablePlusBar.vue';
import { onMounted, ref } from 'vue';
import { columns, editMap, onCancel, onEdit, onSave } from '@/views/i18n/language-type-setting/utils/columns';
import { DeleteFilled, EditPen, Plus } from '@element-plus/icons-vue';
import { useI18nTypeStore } from '@/store/i18n/i18nType';
import { messageBox } from '@/utils/message';
import AddLanguageType from '@/views/i18n/language-type-setting/add-language-tyoe.vue';
const column = ref(columns);
const i18nTypeStore = useI18nTypeStore();
/**
* * 获取多语言类型
*/
const getLanguageType = () => {
i18nTypeStore.getLanguageType();
};
/**
* * 表格的列添加语言类型
*/
const onColumnAdd = () => {
i18nTypeStore.isAddShown = true;
};
/**
* * 删除多语言类容
*/
const onColumnDelete = async (row: any, isPhysicalDelete: boolean) => {
const id = row.id;
const title = isPhysicalDelete ? '物理删除不可逆!' : '确认删除吗?';
messageBox({
message: `删除这条 【${row.summary}`,
title,
showMessage: false,
confirmMessage: undefined,
cancelMessage: '取消删除',
})
.then(result => {
if (result) {
//
isPhysicalDelete && i18nTypeStore.removeLanguageType(id);
//
!isPhysicalDelete && i18nTypeStore.deleteLanguageType(id);
}
return result;
})
.then(result => result && getLanguageType());
};
onMounted(() => {
getLanguageType();
});
</script>
<template>
<div>
<!-- 添加多语言种类 -->
<add-language-type />
<TablePlusBar
:column="column"
:data-list="i18nTypeStore.dataList"
:loading="i18nTypeStore.loading"
:on-re-fresh="getLanguageType"
:table-delete="onColumnDelete"
:table-query-form-visible="false"
@changeColumn="args => (column = args)"
>
<!-- 表格头部按钮 -->
<template #tableButtons>
<el-button :icon="Plus" type="primary" @click="onColumnAdd">{{ $t('buttons.add') }}</el-button>
</template>
<template #op="{ row, index }">
<div v-if="!editMap[index]?.editable" class="flex justify-around">
<el-button :icon="EditPen" link type="warning" @click="onEdit(row, index)">修改</el-button>
<el-dropdown :hide-on-click="false">
<el-link :icon="DeleteFilled" :underline="false" type="danger"> 删除选项</el-link>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item>
<el-button :icon="DeleteFilled" link type="warning" @click="onColumnDelete(row, false)">删除 </el-button>
</el-dropdown-item>
<el-dropdown-item>
<el-button :icon="DeleteFilled" link type="danger" @click="onColumnDelete(row, true)">彻底删除 </el-button>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</div>
<div v-else>
<el-button class="reset-margin" link type="primary" @click="onSave(row, index)"> 保存</el-button>
<el-button class="reset-margin" link @click="onCancel(index)"> 取消</el-button>
</div>
</template>
</TablePlusBar>
</div>
</template>

View File

@ -1,5 +1,5 @@
<script lang="ts" setup> <script lang="ts" setup>
import { ref } from 'vue'; import { onMounted, ref } from 'vue';
import ReCol from '@/components/MyCol'; import ReCol from '@/components/MyCol';
import { formRules } from './utils/rule'; import { formRules } from './utils/rule';
import { FormProps } from './utils/types'; import { FormProps } from './utils/types';
@ -8,6 +8,7 @@ import Segmented from '@/components/ReSegmented';
import ReAnimateSelector from '@/components/AnimateSelector'; import ReAnimateSelector from '@/components/AnimateSelector';
import { fixedTagOptions, frameLoadingOptions, hiddenTagOptions, keepAliveOptions, menuTypeOptions, showLinkOptions, showParentOptions } from '@/enums'; import { fixedTagOptions, frameLoadingOptions, hiddenTagOptions, keepAliveOptions, menuTypeOptions, showLinkOptions, showParentOptions } from '@/enums';
import { $t } from '@/plugins/i18n'; import { $t } from '@/plugins/i18n';
import { userMenuIconStore } from '@/store/modules/menuIcon';
const props = withDefaults(defineProps<FormProps>(), { const props = withDefaults(defineProps<FormProps>(), {
formInline: () => ({ formInline: () => ({
@ -33,9 +34,14 @@ const props = withDefaults(defineProps<FormProps>(), {
}), }),
}); });
const menuIconStore = userMenuIconStore();
const ruleFormRef = ref(); const ruleFormRef = ref();
const newFormInline = ref(props.formInline); const newFormInline = ref(props.formInline);
onMounted(() => {
menuIconStore.getMenuIconList();
});
defineExpose({ menuFormRef: ruleFormRef }); defineExpose({ menuFormRef: ruleFormRef });
</script> </script>

View File

@ -1,13 +1,13 @@
<script lang="ts" setup> <script lang="ts" setup>
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { $t } from '@/plugins/i18n'; import { $t } from '@/plugins/i18n';
import { PureTableBar } from '@/components/TableBar'; import PureTableBar from '@/components/TableBar/src/bar';
import { useRenderIcon } from '@/components/ReIcon/src/hooks'; import { useRenderIcon } from '@/components/ReIcon/src/hooks';
import Delete from '@iconify-icons/ep/delete'; import Delete from '@iconify-icons/ep/delete';
import EditPen from '@iconify-icons/ep/edit-pen'; import EditPen from '@iconify-icons/ep/edit-pen';
import Refresh from '@iconify-icons/ep/refresh'; import Refresh from '@iconify-icons/ep/refresh';
import AddFill from '@iconify-icons/ri/add-circle-line'; import AddFill from '@iconify-icons/ri/add-circle-line';
import { dataList, handleDelete, handleSelectionChange, loading, onSearch, openDialog, resetForm } from '@/views/menu/utils/hook'; import { dataList, handleDelete, loading, onSearch, openDialog, resetForm } from '@/views/menu/utils/hook';
import form from '@/views/role/form.vue'; import form from '@/views/role/form.vue';
import PureTable from '@pureadmin/table'; import PureTable from '@pureadmin/table';
import { columns } from '@/views/menu/utils/rule'; import { columns } from '@/views/menu/utils/rule';
@ -36,7 +36,7 @@ onMounted(() => {
</el-form-item> </el-form-item>
</el-form> </el-form>
<PureTableBar :columns="columns" :isExpandAll="false" :tableRef="tableRef?.getTableRef()" title="菜单管理(仅演示,操作后不生效)" @fullscreen="tableRef.value.setAdaptive()" @refresh="onSearch"> <PureTableBar :columns="columns" :isExpandAll="false" :tableRef="tableRef?.getTableRef()" title="菜单管理" @fullscreen="tableRef.setAdaptive()" @refresh="onSearch">
<template #buttons> <template #buttons>
<el-button :icon="useRenderIcon(AddFill)" type="primary" @click="openDialog()"> 新增菜单</el-button> <el-button :icon="useRenderIcon(AddFill)" type="primary" @click="openDialog()"> 新增菜单</el-button>
</template> </template>
@ -61,7 +61,7 @@ onMounted(() => {
<el-button v-show="row.menuType !== 3" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="openDialog('新增', { parentId: row.id } as any)"> <el-button v-show="row.menuType !== 3" :icon="useRenderIcon(AddFill)" :size="size" class="reset-margin" link type="primary" @click="openDialog('新增', { parentId: row.id } as any)">
新增 新增
</el-button> </el-button>
<el-popconfirm :title="`是否确认删除菜单名称为${$t(row.title)}的这条数据${row?.children?.length > 0 ? '注意下级菜单也会一并删除,请谨慎操作' : ''}`" @confirm="handleDelete(row)"> <el-popconfirm :title="`是否确认删除菜单名称为${$t(row.title)}的这条数据${row?.children?.length > 0 ? '注意下级菜单也会一并删除,请谨慎操作' : ''}`" @confirm="handleDelete(row)">
<template #reference> <template #reference>
<el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary"> 删除 </el-button> <el-button :icon="useRenderIcon(Delete)" :size="size" class="reset-margin" link type="primary"> 删除 </el-button>
</template> </template>

View File

@ -34,14 +34,6 @@ export const getMenuType = (type, text = false) => {
} }
}; };
/**
*
* @param val
*/
export const handleSelectionChange = val => {
console.log('handleSelectionChange', val);
};
/** /**
* *
* @param formEl * @param formEl
@ -52,6 +44,10 @@ export const resetForm = async formEl => {
await onSearch(); await onSearch();
}; };
export const handleSelectionChange = (val: any) => {
console.log('handleSelectionChange', val);
};
export const onSearch = async () => { export const onSearch = async () => {
loading.value = true; loading.value = true;