fix:修改
This commit is contained in:
2
components.d.ts
vendored
2
components.d.ts
vendored
@@ -14,6 +14,7 @@ declare module 'vue' {
|
|||||||
CardItem: typeof import('./src/components/cardItem/index.vue')['default']
|
CardItem: typeof import('./src/components/cardItem/index.vue')['default']
|
||||||
Comment: typeof import('./src/components/comment/index.vue')['default']
|
Comment: typeof import('./src/components/comment/index.vue')['default']
|
||||||
CommonFilter: typeof import('./src/components/commonFilter/index.vue')['default']
|
CommonFilter: typeof import('./src/components/commonFilter/index.vue')['default']
|
||||||
|
DynamicSvgIcon: typeof import('./src/components/dynamicSvgIcon/index.vue')['default']
|
||||||
ElAside: typeof import('element-plus/es')['ElAside']
|
ElAside: typeof import('element-plus/es')['ElAside']
|
||||||
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
ElAvatar: typeof import('element-plus/es')['ElAvatar']
|
||||||
ElBadge: typeof import('element-plus/es')['ElBadge']
|
ElBadge: typeof import('element-plus/es')['ElBadge']
|
||||||
@@ -75,6 +76,7 @@ declare module 'vue' {
|
|||||||
StandardMenu: typeof import('./src/components/standardMenu/index.vue')['default']
|
StandardMenu: typeof import('./src/components/standardMenu/index.vue')['default']
|
||||||
StandMenu: typeof import('./src/components/standMenu/index.vue')['default']
|
StandMenu: typeof import('./src/components/standMenu/index.vue')['default']
|
||||||
Xxx: typeof import('./src/components/comment/xxx.vue')['default']
|
Xxx: typeof import('./src/components/comment/xxx.vue')['default']
|
||||||
|
Xxxx: typeof import('./src/components/xxxx/index.vue')['default']
|
||||||
}
|
}
|
||||||
export interface GlobalDirectives {
|
export interface GlobalDirectives {
|
||||||
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
vLoading: typeof import('element-plus/es')['ElLoadingDirective']
|
||||||
|
|||||||
@@ -30,7 +30,6 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { Filter, Download } from "@element-plus/icons-vue";
|
|
||||||
const filterPopover = ref(null);
|
const filterPopover = ref(null);
|
||||||
|
|
||||||
// 定义事件:重置、应用、下载
|
// 定义事件:重置、应用、下载
|
||||||
|
|||||||
71
src/components/dynamicSvgIcon/index.vue
Normal file
71
src/components/dynamicSvgIcon/index.vue
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
<template>
|
||||||
|
<i class="svg-icon" :style="iconStyle" v-html="processedContent" v-if="content"></i>
|
||||||
|
<i v-else class="svg-icon" :style="iconStyle">
|
||||||
|
<svg
|
||||||
|
viewBox="0 0 1024 1024"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
width="1em"
|
||||||
|
height="1em"
|
||||||
|
>
|
||||||
|
<path v-for="(d, index) in paths" :key="index" :d="d" fill="currentColor" />
|
||||||
|
</svg>
|
||||||
|
</i>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { computed } from 'vue';
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// 预设图标名称
|
||||||
|
name: { type: String, default: '' },
|
||||||
|
// 外部传递的完整 SVG 字符串
|
||||||
|
content: { type: String, default: '' },
|
||||||
|
size: { type: [Number, String], default: 16 },
|
||||||
|
color: { type: String, default: '' }
|
||||||
|
});
|
||||||
|
|
||||||
|
// 预设路径数据
|
||||||
|
const iconData: Record<string, string[]> = {
|
||||||
|
building: [
|
||||||
|
"M832 128H192a32 32 0 0 0-32 32v704a32 32 0 0 0 32 32h640a32 32 0 0 0 32-32V160a32 32 0 0 0-32-32zm-64 704H256V192h512v640z",
|
||||||
|
"M320 256h384v64H320zm0 160h384v64H320zm0 160h384v64H320zM448 704h128v128H448z"
|
||||||
|
],
|
||||||
|
flag: [
|
||||||
|
"M288 128a32 32 0 0 0-32 32v704a32 32 0 0 0 64 0V544h448l-64-160 64-160H320V160a32 32 0 0 0-32-32zM320 288h352l-32 80 32 80H320V288z"
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
// 处理外部传入的 SVG 字符串,确保它能继承颜色
|
||||||
|
const processedContent = computed(() => {
|
||||||
|
if (!props.content) return '';
|
||||||
|
// 简单处理:确保外部 SVG 内部使用 currentColor
|
||||||
|
return props.content.replace(/fill="[^"]*"/g, 'fill="currentColor"');
|
||||||
|
});
|
||||||
|
|
||||||
|
const paths = computed(() => iconData[props.name] || []);
|
||||||
|
|
||||||
|
const iconStyle = computed(() => ({
|
||||||
|
fontSize: typeof props.size === 'number' ? `${props.size}px` : props.size,
|
||||||
|
color: props.color || 'inherit',
|
||||||
|
width: '1em',
|
||||||
|
height: '1em',
|
||||||
|
display: 'inline-flex'
|
||||||
|
}));
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.svg-icon {
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 0;
|
||||||
|
vertical-align: middle;
|
||||||
|
transition: color 0.2s, fill 0.2s; // 添加平滑过渡
|
||||||
|
|
||||||
|
:deep(svg) {
|
||||||
|
width: 1em;
|
||||||
|
height: 1em;
|
||||||
|
fill: currentColor; // 默认使用父级 color
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -42,7 +42,6 @@
|
|||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted, computed, nextTick, watch } from "vue";
|
import { ref, onMounted, onUnmounted, computed, nextTick, watch } from "vue";
|
||||||
import { ArrowDown } from "@element-plus/icons-vue";
|
|
||||||
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
modelValue: [String, Number],
|
modelValue: [String, Number],
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="pro-table-container">
|
<div ref="tableContainerRef" class="pro-table-container">
|
||||||
<el-table
|
<el-table
|
||||||
:data="data"
|
:data="data"
|
||||||
v-bind="$attrs"
|
v-bind="$attrs"
|
||||||
v-loading="innerLoading"
|
v-loading="innerLoading"
|
||||||
class="hover-action-table"
|
class="hover-action-table"
|
||||||
header-row-class-name="header-row-name"
|
header-row-class-name="header-row-name"
|
||||||
|
:height="tableHeight"
|
||||||
>
|
>
|
||||||
<template v-for="(col, index) in columns" :key="col.prop">
|
<template v-for="(col, index) in columns" :key="col.prop">
|
||||||
<el-table-column
|
<el-table-column
|
||||||
@@ -133,7 +134,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed } from "vue";
|
import { ref, computed } from "vue";
|
||||||
import { ArrowDown } from "@element-plus/icons-vue";
|
|
||||||
const props = defineProps({
|
const props = defineProps({
|
||||||
columns: { type: Array, required: true },
|
columns: { type: Array, required: true },
|
||||||
data: { type: Array, required: true },
|
data: { type: Array, required: true },
|
||||||
@@ -159,6 +159,37 @@ const innerLoading = ref(false);
|
|||||||
// 默认按钮长度
|
// 默认按钮长度
|
||||||
const MAX_BUTTON_LENGTH = 3;
|
const MAX_BUTTON_LENGTH = 3;
|
||||||
|
|
||||||
|
// 高度模块的计算
|
||||||
|
const tableContainerRef = ref(null);
|
||||||
|
const containerHeight = ref(0);
|
||||||
|
const minHeight = 400; // 最小高度值
|
||||||
|
// 监听窗口大小变化
|
||||||
|
const handleResize = () => {
|
||||||
|
updateContainerHeight();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 动态计算表格容器高度
|
||||||
|
const updateContainerHeight = async () => {
|
||||||
|
await nextTick();
|
||||||
|
if (tableContainerRef.value) {
|
||||||
|
// 获取容器相对于视口的位置
|
||||||
|
const rect = tableContainerRef.value.getBoundingClientRect();
|
||||||
|
const containerTop = rect.top;
|
||||||
|
|
||||||
|
// 计算从容器位置到浏览器底部的可用高度
|
||||||
|
const availableHeight = window.innerHeight - containerTop;
|
||||||
|
containerHeight.value = Math.max(availableHeight, minHeight);
|
||||||
|
} else {
|
||||||
|
// 如果元素尚未渲染,使用默认偏移值
|
||||||
|
containerHeight.value = Math.max(window.innerHeight - 100, minHeight);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const tableHeight = computed(() => {
|
||||||
|
// 为页码预留空间,大约60px
|
||||||
|
return (containerHeight.value - 60) + 'px';
|
||||||
|
});
|
||||||
|
|
||||||
// 标记是否是首次挂载
|
// 标记是否是首次挂载
|
||||||
let isFirstMount = true;
|
let isFirstMount = true;
|
||||||
|
|
||||||
@@ -208,7 +239,9 @@ const handleSizeChange = (val) => {
|
|||||||
if (props.requestApi) refresh();
|
if (props.requestApi) refresh();
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(async () => {
|
||||||
|
await updateContainerHeight();
|
||||||
|
window.addEventListener('resize', handleResize);
|
||||||
if (props.immediate) {
|
if (props.immediate) {
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
@@ -218,10 +251,16 @@ onMounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 兼容设置了keep-alive
|
// 兼容设置了keep-alive
|
||||||
onActivated(() => {
|
onActivated(async () => {
|
||||||
if (!isFirstMount && props.refreshOnActivated) {
|
if (!isFirstMount && props.refreshOnActivated) {
|
||||||
refresh();
|
refresh();
|
||||||
}
|
}
|
||||||
|
await updateContainerHeight();
|
||||||
|
});
|
||||||
|
|
||||||
|
// 组件销毁的生命周期
|
||||||
|
onUnmounted(() => {
|
||||||
|
window.removeEventListener('resize', handleResize);
|
||||||
});
|
});
|
||||||
|
|
||||||
// 暴露 refresh 方法给外部使用
|
// 暴露 refresh 方法给外部使用
|
||||||
|
|||||||
@@ -29,7 +29,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Location } from '@element-plus/icons-vue'
|
|
||||||
defineOptions({ name: "standardMenu" })
|
defineOptions({ name: "standardMenu" })
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
|
|
||||||
|
|||||||
52
src/hooks/useRelativeTime.ts
Normal file
52
src/hooks/useRelativeTime.ts
Normal file
@@ -0,0 +1,52 @@
|
|||||||
|
// useRelativeTime.ts
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||||
|
import 'dayjs/locale/zh-cn' // 引入中文语言包
|
||||||
|
|
||||||
|
// 初始化插件和语言
|
||||||
|
dayjs.extend(relativeTime)
|
||||||
|
dayjs.locale('zh-cn')
|
||||||
|
|
||||||
|
export function useRelativeTime() {
|
||||||
|
/**
|
||||||
|
* 核心转换函数
|
||||||
|
* @param value 时间戳、Date对象或ISO字符串
|
||||||
|
*/
|
||||||
|
const formatTime = (value: string | number | Date): string => {
|
||||||
|
if (!value) return ''
|
||||||
|
|
||||||
|
const target = dayjs(value)
|
||||||
|
const now = dayjs()
|
||||||
|
const diffSeconds = now.diff(target, 'second')
|
||||||
|
const diffDays = now.diff(target, 'day')
|
||||||
|
|
||||||
|
// 1. 极短时间内(60秒内)显示“刚刚”
|
||||||
|
if (diffSeconds < 60) {
|
||||||
|
return '刚刚'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 一天以内(显示 10分钟前, 1小时前等)
|
||||||
|
if (diffDays < 1) {
|
||||||
|
return target.fromNow()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. 超过一天但在一年以内(显示 10-27)
|
||||||
|
if (diffDays < 365) {
|
||||||
|
return target.format('MM-DD HH:mm')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. 超过一年(显示 2023-10-27)
|
||||||
|
return target.format('YYYY-MM-DD')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果你需要判断“是否超过可删除时间”(比如5分钟内可删除)
|
||||||
|
const canAction = (value: string | number | Date, limitMinutes: number = 5) => {
|
||||||
|
return dayjs().diff(dayjs(value), 'minute') < limitMinutes
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
dayjs, // 同时也暴露原生的 dayjs 方便外部灵活使用
|
||||||
|
formatTime,
|
||||||
|
canAction
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,16 +8,13 @@ import zhCn from "element-plus/es/locale/lang/zh-cn";
|
|||||||
import en from "element-plus/es/locale/lang/en";
|
import en from "element-plus/es/locale/lang/en";
|
||||||
import Directives from '@/utils/directives';
|
import Directives from '@/utils/directives';
|
||||||
import '@/styles/common.scss';
|
import '@/styles/common.scss';
|
||||||
|
import * as ElementPlusIconsVue from '@element-plus/icons-vue'
|
||||||
// 全局导入element ui样式类
|
// 全局导入element ui样式类
|
||||||
import 'element-plus/es/components/message/style/css'
|
import 'element-plus/es/components/message/style/css'
|
||||||
import 'element-plus/es/components/notification/style/css'
|
import 'element-plus/es/components/notification/style/css'
|
||||||
import 'element-plus/es/components/message-box/style/css'
|
import 'element-plus/es/components/message-box/style/css'
|
||||||
import 'element-plus/es/components/loading/style/css'
|
import 'element-plus/es/components/loading/style/css'
|
||||||
|
|
||||||
const pinia = createPinia();
|
|
||||||
const app = createApp(App);
|
|
||||||
|
|
||||||
// 导入全局的i18n文件
|
// 导入全局的i18n文件
|
||||||
const loadLocalMessages = async () => {
|
const loadLocalMessages = async () => {
|
||||||
const messages: Record<string, any> = {};
|
const messages: Record<string, any> = {};
|
||||||
@@ -55,7 +52,9 @@ const getLocalLang = () => {
|
|||||||
const initApp = async () => {
|
const initApp = async () => {
|
||||||
const pinia = createPinia();
|
const pinia = createPinia();
|
||||||
const app = createApp(App);
|
const app = createApp(App);
|
||||||
|
for (const [key, component] of Object.entries(ElementPlusIconsVue)) {
|
||||||
|
app.component(key, component)
|
||||||
|
}
|
||||||
// 加载语言消息
|
// 加载语言消息
|
||||||
const messages = await loadLocalMessages();
|
const messages = await loadLocalMessages();
|
||||||
const elementLocale = getLocalLang() === "zh" ? zhCn : en;
|
const elementLocale = getLocalLang() === "zh" ? zhCn : en;
|
||||||
|
|||||||
@@ -86,7 +86,7 @@
|
|||||||
<!-- 当前用户信息展示 -->
|
<!-- 当前用户信息展示 -->
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<span class="nickname">{{ item.nickname }}</span>
|
<span class="nickname">{{ item.nickname }}</span>
|
||||||
<span class="time">{{ item.time }}</span>
|
<span class="time">{{ formatTime(item.time) }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- 回复内容模块 -->
|
<!-- 回复内容模块 -->
|
||||||
@@ -125,7 +125,7 @@
|
|||||||
<span class="reply-text">回复</span>
|
<span class="reply-text">回复</span>
|
||||||
<span class="target-name">@{{ reply.replyTo }}</span>
|
<span class="target-name">@{{ reply.replyTo }}</span>
|
||||||
</div>
|
</div>
|
||||||
<span class="time">{{ reply.time }}</span>
|
<span class="time">{{ formatTime(reply.time) }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="content-body">{{ reply.content }}</div>
|
<div class="content-body">{{ reply.content }}</div>
|
||||||
<!-- 回复 删除功能 -->
|
<!-- 回复 删除功能 -->
|
||||||
@@ -191,22 +191,17 @@
|
|||||||
import { ref, reactive } from "vue";
|
import { ref, reactive } from "vue";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import EmojiPicker from "./EmojiPicker.vue";
|
import EmojiPicker from "./EmojiPicker.vue";
|
||||||
import {
|
|
||||||
Picture,
|
|
||||||
Paperclip,
|
|
||||||
Promotion,
|
|
||||||
ChatDotSquare,
|
|
||||||
Delete,
|
|
||||||
} from "@element-plus/icons-vue";
|
|
||||||
import NameAvatar from "@/components/nameAvatar/index.vue";
|
import NameAvatar from "@/components/nameAvatar/index.vue";
|
||||||
import { useI18n } from "vue-i18n";
|
import { useI18n } from "vue-i18n";
|
||||||
import { useUserStore } from "@/store";
|
import { useUserStore } from "@/store";
|
||||||
|
import { useRelativeTime } from '@/hooks/useRelativeTime'
|
||||||
|
const { formatTime } = useRelativeTime()
|
||||||
const userStore = useUserStore();
|
const userStore = useUserStore();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
// 当前用户信息
|
// 当前用户信息
|
||||||
const currentUser = computed(() => {
|
const currentUser = computed(() => {
|
||||||
return {
|
return {
|
||||||
nickname: userStore.userInfo.nickname,
|
nickname: userStore.userInfo.username,
|
||||||
avatar: userStore.userInfo.avatar,
|
avatar: userStore.userInfo.avatar,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
@@ -216,13 +211,14 @@ const activeReply = reactive({ parentId: null, targetName: "" });
|
|||||||
const mainInput = ref("");
|
const mainInput = ref("");
|
||||||
const replyInput = ref("");
|
const replyInput = ref("");
|
||||||
const loading = ref(true); //当前骨架屏显示
|
const loading = ref(true); //当前骨架屏显示
|
||||||
|
// 评论数据
|
||||||
const commentData = ref([
|
const commentData = ref([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
nickname: "李星倩",
|
nickname: "李星倩",
|
||||||
avatar: "",
|
avatar: "",
|
||||||
content: "已完成ROI测算,请审核。",
|
content: "已完成ROI测算,请审核。",
|
||||||
time: "10分钟前",
|
time: "2023-10-27T14:30:00",
|
||||||
canDelete: true,
|
canDelete: true,
|
||||||
children: [
|
children: [
|
||||||
{
|
{
|
||||||
@@ -231,7 +227,7 @@ const commentData = ref([
|
|||||||
replyTo: "李星倩",
|
replyTo: "李星倩",
|
||||||
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=2",
|
avatar: "https://api.dicebear.com/7.x/avataaars/svg?seed=2",
|
||||||
content: "收到,数据已入库,我马上看下。",
|
content: "收到,数据已入库,我马上看下。",
|
||||||
time: "刚刚",
|
time: 1767604936684,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
@@ -271,9 +267,11 @@ const onSelectEmoji = (emoji, type) => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 取消评论
|
||||||
const cancelReply = () => {
|
const cancelReply = () => {
|
||||||
activeReply.groupId = null;
|
activeReply.groupId = null;
|
||||||
};
|
};
|
||||||
|
|
||||||
// @ 识别解析函数
|
// @ 识别解析函数
|
||||||
const parseMention = (text) => {
|
const parseMention = (text) => {
|
||||||
if (!text) return "";
|
if (!text) return "";
|
||||||
@@ -297,7 +295,7 @@ const submitMainComment = () => {
|
|||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
...currentUser.value,
|
...currentUser.value,
|
||||||
content: mainInput.value,
|
content: mainInput.value,
|
||||||
time: "刚刚",
|
time: new Date().valueOf(),
|
||||||
canDelete: true,
|
canDelete: true,
|
||||||
children: [],
|
children: [],
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- 展开收缩左侧菜单按钮 -->
|
<!-- 展开收缩左侧菜单按钮 -->
|
||||||
<div class="mj-collapse" @click="showCollapse">
|
<div class="mj-collapse" @click="showCollapse">
|
||||||
<el-icon><component :is="isCollapse ? DArrowRight : DArrowLeft" /></el-icon>
|
<el-icon><component :is="isCollapse ? 'DArrowRight' : 'DArrowLeft'" /></el-icon>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-aside>
|
</el-aside>
|
||||||
@@ -48,7 +48,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import standardMenu from "@/components/standardMenu/index.vue";
|
import standardMenu from "@/components/standardMenu/index.vue";
|
||||||
import { useUserStore } from "@/store";
|
import { useUserStore } from "@/store";
|
||||||
import { DArrowLeft, DArrowRight } from "@element-plus/icons-vue";
|
|
||||||
import rightMenuGroup from './rightMenuGroup.vue';
|
import rightMenuGroup from './rightMenuGroup.vue';
|
||||||
import companyLogo from '@/assets/images/logo.png';
|
import companyLogo from '@/assets/images/logo.png';
|
||||||
defineOptions({
|
defineOptions({
|
||||||
|
|||||||
@@ -48,7 +48,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Monitor, Bell } from "@element-plus/icons-vue";
|
|
||||||
import TokenManager from "@/utils/storage";
|
import TokenManager from "@/utils/storage";
|
||||||
import NameAvatar from "@/components/nameAvatar/index.vue";
|
import NameAvatar from "@/components/nameAvatar/index.vue";
|
||||||
import { useUserStore } from "@/store";
|
import { useUserStore } from "@/store";
|
||||||
|
|||||||
@@ -87,7 +87,6 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
||||||
import { Message, Lock, Right } from "@element-plus/icons-vue";
|
|
||||||
import { login } from "@/api";
|
import { login } from "@/api";
|
||||||
import { useUserStore } from "@/store";
|
import { useUserStore } from "@/store";
|
||||||
import TokenManager from '@/utils/storage';
|
import TokenManager from '@/utils/storage';
|
||||||
|
|||||||
@@ -145,7 +145,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import CommonTable from "@/components/proTable/index.vue";
|
import CommonTable from "@/components/proTable/index.vue";
|
||||||
import dayjs from "dayjs";
|
import dayjs from "dayjs";
|
||||||
import { Search, Filter, Plus } from "@element-plus/icons-vue";
|
|
||||||
import dictFieldLevelManage from "./dictFieldLevelManage.vue";
|
import dictFieldLevelManage from "./dictFieldLevelManage.vue";
|
||||||
import { DictManage } from "@/dict";
|
import { DictManage } from "@/dict";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@@ -62,7 +62,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref, onMounted } from "vue";
|
import { reactive, ref, onMounted } from "vue";
|
||||||
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
||||||
import { QuestionFilled } from "@element-plus/icons-vue";
|
|
||||||
import { saveDictTypeValue, updateDictTypeValue } from "@/api/stage/dict";
|
import { saveDictTypeValue, updateDictTypeValue } from "@/api/stage/dict";
|
||||||
defineOptions({ name: "DictFieldLevelManage" });
|
defineOptions({ name: "DictFieldLevelManage" });
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|||||||
@@ -58,7 +58,6 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { reactive, ref, onMounted } from "vue";
|
import { reactive, ref, onMounted } from "vue";
|
||||||
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
import { ElMessage, type FormInstance, type FormRules } from "element-plus";
|
||||||
import { QuestionFilled } from "@element-plus/icons-vue";
|
|
||||||
import { addDictValue, updateDictValue } from "@/api/stage/dict";
|
import { addDictValue, updateDictValue } from "@/api/stage/dict";
|
||||||
defineOptions({ name: "DictManage" });
|
defineOptions({ name: "DictManage" });
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
<div class="mj-dict">
|
<div class="mj-dict">
|
||||||
<stageBreadcrumbs title="组织管理">
|
<stageBreadcrumbs title="组织管理">
|
||||||
<template #content>
|
<template #content>
|
||||||
<el-button :icon="Plus" type="primary" @click="addDict"
|
<el-button :icon="'Plus'" type="primary" @click="addDict"
|
||||||
>新增字典</el-button
|
>新增字典</el-button
|
||||||
>
|
>
|
||||||
</template>
|
</template>
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
<el-input
|
<el-input
|
||||||
placeholder="搜索字典..."
|
placeholder="搜索字典..."
|
||||||
class="auto-expand-input"
|
class="auto-expand-input"
|
||||||
:prefix-icon="Search"
|
:prefix-icon="'Search'"
|
||||||
v-model="searchVal"
|
v-model="searchVal"
|
||||||
@keyup.enter="fetchTableData"
|
@keyup.enter="fetchTableData"
|
||||||
></el-input>
|
></el-input>
|
||||||
@@ -116,7 +116,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Plus, Search } from "@element-plus/icons-vue";
|
|
||||||
import CommonTable from "@/components/proTable/index.vue";
|
import CommonTable from "@/components/proTable/index.vue";
|
||||||
import dictFieldConfig from "./dictFieldConfig.vue";
|
import dictFieldConfig from "./dictFieldConfig.vue";
|
||||||
import dictManage from "./dictManage.vue";
|
import dictManage from "./dictManage.vue";
|
||||||
|
|||||||
@@ -32,8 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { Timer } from "@element-plus/icons-vue";
|
|
||||||
|
|
||||||
const logData = [
|
const logData = [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -17,8 +17,8 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="right-actions">
|
<div class="right-actions">
|
||||||
<el-button plain :icon="EditPen">编辑配置</el-button>
|
<el-button plain :icon="'EditPen'">编辑配置</el-button>
|
||||||
<el-button type="danger" plain :icon="CircleClose">禁用</el-button>
|
<el-button :type="turnOn ? 'success':'danger'" plain :icon="turnOn ? 'CircleCheck' : 'CircleClose'" @click="onButtonCheck">{{turnOn ? '启用' : '禁用'}}</el-button>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</el-card>
|
</el-card>
|
||||||
@@ -78,9 +78,9 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup lang="ts">
|
||||||
import { OfficeBuilding, EditPen, CircleClose } from "@element-plus/icons-vue";
|
|
||||||
|
|
||||||
|
const turnOn = ref(true);
|
||||||
const systemList = [
|
const systemList = [
|
||||||
{
|
{
|
||||||
name: "生效时间",
|
name: "生效时间",
|
||||||
@@ -120,6 +120,12 @@ const baseList = [
|
|||||||
value: "赵康, 李思奇, 董峥",
|
value: "赵康, 李思奇, 董峥",
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
||||||
|
// 禁用-启用功能
|
||||||
|
const onButtonCheck = () =>{
|
||||||
|
turnOn.value = !turnOn.value;
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
|||||||
168
src/pages/stage/origanization/addOrgan.vue
Normal file
168
src/pages/stage/origanization/addOrgan.vue
Normal file
@@ -0,0 +1,168 @@
|
|||||||
|
<template>
|
||||||
|
<div class="enterprise-dialog-wrapper">
|
||||||
|
<el-dialog
|
||||||
|
v-model="dialogVisible"
|
||||||
|
title="创建企业配置"
|
||||||
|
width="500px"
|
||||||
|
:show-close="true"
|
||||||
|
class="standard-ui-dialog"
|
||||||
|
>
|
||||||
|
<template #header>
|
||||||
|
<div class="dialog-header">
|
||||||
|
<div class="header-icon">
|
||||||
|
<el-icon><OfficeBuilding /></el-icon>
|
||||||
|
</div>
|
||||||
|
<div class="header-text">
|
||||||
|
<h3>创建企业配置</h3>
|
||||||
|
<span>对接企微组织架构</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<el-form :model="form" layout="vertical" label-position="top">
|
||||||
|
<el-form-item label="企业名称">
|
||||||
|
<el-input v-model="form.name" placeholder="如:名匠视界" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="域名">
|
||||||
|
<el-input v-model="form.domain" placeholder="example.com">
|
||||||
|
<template #prefix
|
||||||
|
><el-icon><Global /></el-icon
|
||||||
|
></template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="微信 CORPID">
|
||||||
|
<el-input v-model="form.corpId" placeholder="ww123...">
|
||||||
|
<template #prefix
|
||||||
|
><el-icon><CircleCheck /></el-icon
|
||||||
|
></template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-row :gutter="20">
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="应用 ID">
|
||||||
|
<el-input v-model="form.appId" placeholder="1000001">
|
||||||
|
<template #prefix
|
||||||
|
><el-icon><Postcard /></el-icon
|
||||||
|
></template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
<el-col :span="12">
|
||||||
|
<el-form-item label="应用密钥">
|
||||||
|
<el-input
|
||||||
|
v-model="form.appSecret"
|
||||||
|
type="password"
|
||||||
|
placeholder="••••••••"
|
||||||
|
show-password
|
||||||
|
>
|
||||||
|
<template #prefix
|
||||||
|
><el-icon><Key /></el-icon
|
||||||
|
></template>
|
||||||
|
</el-input>
|
||||||
|
</el-form-item>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
|
||||||
|
<el-form-item label="校验 TOKEN">
|
||||||
|
<el-input v-model="form.token" placeholder="输入校验 Token" />
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="加解密密钥">
|
||||||
|
<el-input v-model="form.aesKey" placeholder="输入 EncodingAESKey" />
|
||||||
|
</el-form-item>
|
||||||
|
</el-form>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<div class="dialog-footer">
|
||||||
|
<el-button @click="dialogVisible = false">取消</el-button>
|
||||||
|
<el-button type="primary" class="btn-confirm" @click="handleSubmit"
|
||||||
|
>确认创建</el-button
|
||||||
|
>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
defineOptions({ name: "AddOrgan" });
|
||||||
|
|
||||||
|
const dialogVisible = ref(true);
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
name: "",
|
||||||
|
domain: "",
|
||||||
|
corpId: "",
|
||||||
|
appId: "",
|
||||||
|
appSecret: "",
|
||||||
|
token: "",
|
||||||
|
aesKey: "",
|
||||||
|
});
|
||||||
|
|
||||||
|
const handleSubmit = () => {};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.enterprise-dialog-wrapper {
|
||||||
|
// 深度覆盖 Element Plus 样式
|
||||||
|
:deep(.el-dialog) {
|
||||||
|
border-radius: 20px;
|
||||||
|
|
||||||
|
.el-form-item__label {
|
||||||
|
font-weight: 600;
|
||||||
|
color: #5f6d7e;
|
||||||
|
padding-bottom: 4px;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-input__wrapper {
|
||||||
|
background-color: #f7f9fb;
|
||||||
|
box-shadow: none;
|
||||||
|
border: 1px solid #e2e8f0;
|
||||||
|
border-radius: 8px;
|
||||||
|
height: 40px;
|
||||||
|
|
||||||
|
&.is-focus {
|
||||||
|
border-color: #2b6df8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 顶部标题布局
|
||||||
|
.dialog-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 15px;
|
||||||
|
|
||||||
|
.header-icon {
|
||||||
|
background-color: #edf2ff;
|
||||||
|
color: #2b6df8;
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 12px;
|
||||||
|
display: flex;
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-text {
|
||||||
|
h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 18px;
|
||||||
|
color: #1a1a1a;
|
||||||
|
}
|
||||||
|
span {
|
||||||
|
font-size: 12px;
|
||||||
|
color: #94a3b8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -7,7 +7,7 @@
|
|||||||
<OverflowTabs v-model="activeTab" :items="tabList" :height="60"/>
|
<OverflowTabs v-model="activeTab" :items="tabList" :height="60"/>
|
||||||
</template>
|
</template>
|
||||||
<template #action>
|
<template #action>
|
||||||
<el-button type="primary" :icon="Plus" plain>新增集团</el-button>
|
<el-button type="primary" :icon="'Plus'" plain>新增集团</el-button>
|
||||||
</template>
|
</template>
|
||||||
</stageBreadcrumbs>
|
</stageBreadcrumbs>
|
||||||
</div>
|
</div>
|
||||||
@@ -19,7 +19,7 @@
|
|||||||
<div class="org-tree-search">
|
<div class="org-tree-search">
|
||||||
<el-input
|
<el-input
|
||||||
v-model="search"
|
v-model="search"
|
||||||
:prefix-icon="Search"
|
:prefix-icon="'Search'"
|
||||||
placeholder="搜索部门或公司"
|
placeholder="搜索部门或公司"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@@ -29,13 +29,32 @@
|
|||||||
:data="data"
|
:data="data"
|
||||||
:props="defaultProps"
|
:props="defaultProps"
|
||||||
@node-click="handleNodeClick"
|
@node-click="handleNodeClick"
|
||||||
/>
|
>
|
||||||
|
<template #default="{ node, data }">
|
||||||
|
<div class="org-tree-item">
|
||||||
|
<div class="org-tree-item-left">
|
||||||
|
<DynamicSvgIcon
|
||||||
|
:name="getIconComponent(node)"
|
||||||
|
:size="15"
|
||||||
|
:hover-color="'#67c23a'"
|
||||||
|
:color="getIconColor(node)"
|
||||||
|
/>
|
||||||
|
<span>{{ node.label }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="org-tree-item-right">
|
||||||
|
<el-icon :size="15">
|
||||||
|
<Delete />
|
||||||
|
</el-icon>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</el-tree>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="org-bottom-add">
|
<div class="org-bottom-add">
|
||||||
<el-input v-model="addValue" placeholder="快速添加分公司...">
|
<el-input v-model="addValue" placeholder="快速添加分公司...">
|
||||||
<template #suffix>
|
<template #suffix>
|
||||||
<el-button text type="primary" :icon="Plus"> </el-button>
|
<el-button text type="primary" :icon="'Plus'"> </el-button>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,15 +70,19 @@
|
|||||||
</el-tabs>
|
</el-tabs>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 添加集团-->
|
||||||
|
<addOrgan />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { Plus, Search } from "@element-plus/icons-vue";
|
|
||||||
import stageBreadcrumbs from "@/components/stageBreadcrumbs/index.vue";
|
import stageBreadcrumbs from "@/components/stageBreadcrumbs/index.vue";
|
||||||
import OverflowTabs from "@/components/overflowTabs/index.vue";
|
import OverflowTabs from "@/components/overflowTabs/index.vue";
|
||||||
import AuditLogs from "./AuditLogs.vue";
|
import AuditLogs from "./auditLogs.vue";
|
||||||
import OrganizationDetail from "./OrganizationDetail.vue";
|
import OrganizationDetail from "./organizationDetail.vue";
|
||||||
import { OfficeBuilding, EditPen, CircleClose } from "@element-plus/icons-vue";
|
import DynamicSvgIcon from '@/components/dynamicSvgIcon/index.vue';
|
||||||
|
import addOrgan from "./addOrgan.vue";
|
||||||
|
|
||||||
defineOptions({ name: "Organization" });
|
defineOptions({ name: "Organization" });
|
||||||
|
|
||||||
const addValue = ref("");
|
const addValue = ref("");
|
||||||
@@ -81,6 +104,22 @@ interface Tree {
|
|||||||
children?: Tree[];
|
children?: Tree[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 获取图标组件
|
||||||
|
const getIconComponent = (node: any) => {
|
||||||
|
if (node.level === 1) {
|
||||||
|
return 'building'; // 一级节点使用
|
||||||
|
} else if (node.level === 2) {
|
||||||
|
return `flag`; // 二级节点使用其他图标
|
||||||
|
} else {
|
||||||
|
return 'flag'; // 更深层级的默认图标
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取图标颜色(可选)
|
||||||
|
const getIconColor = (node: any) => {
|
||||||
|
return '#9EADC2';
|
||||||
|
};
|
||||||
|
|
||||||
const handleNodeClick = (data: Tree) => {
|
const handleNodeClick = (data: Tree) => {
|
||||||
console.log(data);
|
console.log(data);
|
||||||
};
|
};
|
||||||
@@ -184,6 +223,27 @@ const defaultProps = {
|
|||||||
flex: 1;
|
flex: 1;
|
||||||
overflow: auto;
|
overflow: auto;
|
||||||
padding: math.div($mj-padding-standard, 2) $mj-padding-standard;
|
padding: math.div($mj-padding-standard, 2) $mj-padding-standard;
|
||||||
|
.org-tree-item{
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
.org-tree-item-right{
|
||||||
|
color:#A5ADB8;
|
||||||
|
&:hover{
|
||||||
|
color:#2065FC;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-icon){
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
.org-tree-item-left{
|
||||||
|
:deep(.el-icon){
|
||||||
|
margin-right: 3px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.org-bottom-add {
|
.org-bottom-add {
|
||||||
border-top: 1px solid #f1f5f9;
|
border-top: 1px solid #f1f5f9;
|
||||||
|
|||||||
@@ -143,7 +143,44 @@ const addDynamicRoutes = async () => {
|
|||||||
// 从后端获取路由菜单数据 (这边需要区分 后台的菜单 和用户的菜单)
|
// 从后端获取路由菜单数据 (这边需要区分 后台的菜单 和用户的菜单)
|
||||||
let allRoutes:any[] = [];
|
let allRoutes:any[] = [];
|
||||||
if (userStore.isBackendUser) {
|
if (userStore.isBackendUser) {
|
||||||
const backendResponse = await getRouteMenus();
|
// const backendResponse = await getRouteMenus();
|
||||||
|
const backendResponse = [
|
||||||
|
{
|
||||||
|
"name": "字典管理",
|
||||||
|
"code": "dict",
|
||||||
|
"icon": "OfficeBuilding",
|
||||||
|
"metadata": null,
|
||||||
|
"children": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "组织管理",
|
||||||
|
"code": "origanization",
|
||||||
|
"icon": "OfficeBuilding",
|
||||||
|
"metadata": null,
|
||||||
|
"children": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "人员管理",
|
||||||
|
"code": "personnel",
|
||||||
|
"icon": "OfficeBuilding",
|
||||||
|
"metadata": null,
|
||||||
|
"children": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "权限管理",
|
||||||
|
"code": "permission",
|
||||||
|
"icon": "OfficeBuilding",
|
||||||
|
"metadata": null,
|
||||||
|
"children": null
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "流程管理",
|
||||||
|
"code": "flow",
|
||||||
|
"icon": "OfficeBuilding",
|
||||||
|
"metadata": null,
|
||||||
|
"children": null
|
||||||
|
}
|
||||||
|
];
|
||||||
allRoutes = [
|
allRoutes = [
|
||||||
{
|
{
|
||||||
code: "stage",
|
code: "stage",
|
||||||
|
|||||||
6
src/utils/svgIcon.ts
Normal file
6
src/utils/svgIcon.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export const svgIcons: Record<string, string> = {
|
||||||
|
'custom-building': `<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M853.333333 426.666667H725.333333v298.666666H298.666667V426.666667H170.666667v384c0 23.466667 19.2 42.666667 42.666666 42.666667h597.333334c23.466667 0 42.666667-19.2 42.666666-42.666667v-384z m-512-128c0-117.333333 96-213.333333 213.333333-213.333334s213.333333 96 213.333334 213.333334H341.333333z"/></svg>`,
|
||||||
|
'custom-company': `<svg class="icon-svg" viewBox="0 0 1024 1024"><path fill="currentColor" d="M288 128a32 32 0 0 0-32 32v704a32 32 0 0 0 64 0V544h448l-64-160 64-160H320V160a32 32 0 0 0-32-32zM320 288h352l-32 80 32 80H320V288z"/></svg>`,
|
||||||
|
'custom-department': `<svg viewBox="0 0 1024 1024" xmlns="http://www.w3.org/2000/svg"><path d="M853.333333 426.666667H725.333333v298.666666H298.666667V426.666667H170.666667v384c0 23.466667 19.2 42.666667 42.666666 42.666667h597.333334c23.466667 0 42.666667-19.2 42.666666-42.666667v-384z"/></svg>`,
|
||||||
|
// 你可以添加更多 SVG 图标
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user