chore(config): 添加项目配置文件和隐私协议
- 添加 .editorconfig 文件统一代码风格 - 添加 .env.development 和 .env.example 环境配置文件 - 添加 .eslintignore 和 .eslintrc.js 代码检查配置 - 添加 .gitignore 版本控制忽略文件配置 - 添加 .prettierignore 格式化忽略配置 - 添加隐私协议HTML文件 - 添加API密钥管理组件基础结构
This commit is contained in:
109
src/components/ByteMdEditor/index.vue
Normal file
109
src/components/ByteMdEditor/index.vue
Normal file
@@ -0,0 +1,109 @@
|
||||
<!-- markdown 编辑器 -->
|
||||
<template>
|
||||
<div ref="rootRef" class="ele-bytemd-wrap"></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, ref, watch } from 'vue';
|
||||
import { Editor } from 'bytemd';
|
||||
import type { BytemdPlugin, BytemdLocale, ViewerProps } from 'bytemd';
|
||||
import 'bytemd/dist/index.min.css';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value: string;
|
||||
plugins?: BytemdPlugin[];
|
||||
sanitize?: (schema: any) => any;
|
||||
mode?: 'split' | 'tab' | 'auto';
|
||||
previewDebounce?: number;
|
||||
placeholder?: string;
|
||||
editorConfig?: Record<string, any>;
|
||||
locale?: Partial<BytemdLocale>;
|
||||
uploadImages?: (
|
||||
files: File[]
|
||||
) => Promise<Pick<any, 'url' | 'alt' | 'title'>[]>;
|
||||
overridePreview?: (el: HTMLElement, props: ViewerProps) => void;
|
||||
maxLength?: number;
|
||||
height?: string;
|
||||
fullZIndex?: number;
|
||||
}>(),
|
||||
{
|
||||
fullZIndex: 999
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value?: string): void;
|
||||
(e: 'change', value?: string): void;
|
||||
}>();
|
||||
|
||||
const rootRef = ref<HTMLElement | null>(null);
|
||||
const editor = ref<InstanceType<typeof Editor> | null>(null);
|
||||
|
||||
onMounted(() => {
|
||||
editor.value = new Editor({
|
||||
target: rootRef.value as HTMLElement,
|
||||
props
|
||||
});
|
||||
editor.value.$on('change', (e: any) => {
|
||||
emit('update:value', e.detail.value);
|
||||
emit('change', e.detail.value);
|
||||
});
|
||||
});
|
||||
|
||||
watch(
|
||||
[
|
||||
() => props.value,
|
||||
() => props.plugins,
|
||||
() => props.sanitize,
|
||||
() => props.mode,
|
||||
() => props.previewDebounce,
|
||||
() => props.placeholder,
|
||||
() => props.editorConfig,
|
||||
() => props.locale,
|
||||
() => props.uploadImages,
|
||||
() => props.maxLength
|
||||
],
|
||||
() => {
|
||||
const option = { ...props };
|
||||
for (let key in option) {
|
||||
if (typeof option[key] === 'undefined') {
|
||||
delete option[key];
|
||||
}
|
||||
}
|
||||
editor.value?.$set(option);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// 修改编辑器高度
|
||||
.ele-bytemd-wrap :deep(.bytemd) {
|
||||
height: v-bind(height);
|
||||
|
||||
// 修改全屏的 zIndex
|
||||
&.bytemd-fullscreen {
|
||||
z-index: v-bind(fullZIndex);
|
||||
}
|
||||
|
||||
// 去掉默认的最大宽度限制
|
||||
.CodeMirror .CodeMirror-lines {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
pre.CodeMirror-line,
|
||||
pre.CodeMirror-line-like {
|
||||
padding: 0 24px;
|
||||
}
|
||||
|
||||
.markdown-body {
|
||||
max-width: 100%;
|
||||
padding: 16px 24px;
|
||||
}
|
||||
|
||||
// 去掉 github 图标
|
||||
.bytemd-toolbar-right > .bytemd-toolbar-icon:last-child {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
93
src/components/ByteMdViewer/index.vue
Normal file
93
src/components/ByteMdViewer/index.vue
Normal file
@@ -0,0 +1,93 @@
|
||||
<!-- markdown 解析 -->
|
||||
<template>
|
||||
<!-- eslint-disable vue/no-v-html -->
|
||||
<div
|
||||
ref="rootRef"
|
||||
v-html="content"
|
||||
class="markdown-body"
|
||||
@click="handleClick"
|
||||
>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted, onBeforeUnmount, nextTick } from 'vue';
|
||||
import type { BytemdPlugin } from 'bytemd';
|
||||
import { getProcessor } from 'bytemd';
|
||||
|
||||
const props = defineProps<{
|
||||
value: string;
|
||||
plugins?: BytemdPlugin[];
|
||||
sanitize?: (schema: any) => any;
|
||||
}>();
|
||||
|
||||
const rootRef = ref<HTMLElement | null>(null);
|
||||
const content = ref<any | null>(null);
|
||||
const cbs = ref<(void | (() => void))[]>([]);
|
||||
|
||||
const on = () => {
|
||||
if (props.plugins && rootRef.value && content.value) {
|
||||
cbs.value = props.plugins.map(({ viewerEffect }) => {
|
||||
return (
|
||||
viewerEffect &&
|
||||
viewerEffect({
|
||||
markdownBody: rootRef.value as HTMLElement,
|
||||
file: content.value
|
||||
})
|
||||
);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const off = () => {
|
||||
if (cbs.value) {
|
||||
cbs.value.forEach((cb) => cb && cb());
|
||||
}
|
||||
};
|
||||
|
||||
const handleClick = (e: MouseEvent) => {
|
||||
const $ = e.target as HTMLElement;
|
||||
if ($.tagName !== 'A') {
|
||||
return;
|
||||
}
|
||||
|
||||
const href = $.getAttribute('href');
|
||||
if (!href || !href.startsWith('#')) {
|
||||
return;
|
||||
}
|
||||
|
||||
const dest = rootRef.value?.querySelector('#user-content-' + href.slice(1));
|
||||
if (dest) {
|
||||
dest.scrollIntoView();
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
[() => props.value, () => props.plugins, () => props.sanitize],
|
||||
() => {
|
||||
try {
|
||||
content.value = getProcessor({
|
||||
plugins: props.plugins,
|
||||
sanitize: props.sanitize
|
||||
}).processSync(props.value);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
off();
|
||||
nextTick(() => {
|
||||
on();
|
||||
});
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
|
||||
onMounted(() => {
|
||||
on();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
off();
|
||||
});
|
||||
</script>
|
||||
73
src/components/ChooseDictionary/index.vue
Normal file
73
src/components/ChooseDictionary/index.vue
Normal file
@@ -0,0 +1,73 @@
|
||||
<!-- 公共参数数据源 -->
|
||||
<template>
|
||||
<a-select
|
||||
:allow-clear="allowClear"
|
||||
show-search
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
:value="value"
|
||||
class="w-full"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@change="change"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { listDictionaryData } from '@/api/system/dictionary-data';
|
||||
import type { DictionaryData } from '@/api/system/dictionary-data/model';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'index', index: number): void;
|
||||
(e: 'blur'): void;
|
||||
(e: 'done', item: DictionaryData): void;
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
allowClear?: boolean;
|
||||
width?: number;
|
||||
index?: number;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择'
|
||||
}
|
||||
);
|
||||
|
||||
const data = ref<DictionaryData[]>();
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
emit('index', Number(props.index));
|
||||
};
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
|
||||
const change = (e: any, item: any) => {
|
||||
emit('done', item);
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
data.value = [];
|
||||
listDictionaryData({ dictCode: 'NavigationModel' }).then((list) => {
|
||||
data.value = list.map((d) => {
|
||||
return {
|
||||
label: d.dictDataName,
|
||||
value: d.dictDataCode,
|
||||
component: d.component
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
reload();
|
||||
</script>
|
||||
173
src/components/DesignBanner/index.vue
Normal file
173
src/components/DesignBanner/index.vue
Normal file
@@ -0,0 +1,173 @@
|
||||
<template>
|
||||
<div
|
||||
:class="data.key + ' ' + data.class"
|
||||
:style="data.style"
|
||||
@click="onClick"
|
||||
>
|
||||
{{ data.style }}
|
||||
{{ data.class }}
|
||||
<a-carousel arrows autoplay :dots="true">
|
||||
<template #prevArrow>
|
||||
<div class="custom-slick-arrow" style="left: 20px; z-index: 1">
|
||||
<LeftCircleOutlined />
|
||||
</div>
|
||||
</template>
|
||||
<template #nextArrow>
|
||||
<div class="custom-slick-arrow" style="right: 40px">
|
||||
<RightCircleOutlined />
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="form.images">
|
||||
<template v-for="(item, index) in JSON.parse(form.images)" :key="index">
|
||||
<div class="ad-item">
|
||||
<a-image :preview="false" :src="item.url" width="100vw" />
|
||||
<a-space class="ad-text" :size="10" direction="vertical">
|
||||
<div class="title">{{ form.name }}</div>
|
||||
<div class="desc">{{ form.comments }}</div>
|
||||
<div class="btn"
|
||||
><a-button
|
||||
size="large"
|
||||
style="padding: 0 30px"
|
||||
@click="openNew(`${form.path}`)"
|
||||
>了解更多</a-button
|
||||
></div
|
||||
>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-carousel>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { getAd } from '@/api/cms/ad';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import { Ad } from '@/api/cms/ad/model';
|
||||
import { ref } from 'vue';
|
||||
import { openNew } from '@/utils/common';
|
||||
import {
|
||||
LeftCircleOutlined,
|
||||
RightCircleOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
const props = defineProps<{
|
||||
// 是否显示
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: any | null;
|
||||
index?: number | 0;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', index: number): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const visible = ref<boolean>(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, assignFields } = useFormData<Ad>({
|
||||
adId: undefined,
|
||||
name: '',
|
||||
adType: '图片广告',
|
||||
images: '',
|
||||
width: '',
|
||||
height: '',
|
||||
path: '',
|
||||
type: '',
|
||||
status: 0,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
// 幻灯片样式
|
||||
const bodyStyle = {
|
||||
width: '100%'
|
||||
};
|
||||
|
||||
const dotsClass = 'dots';
|
||||
|
||||
const onChange = () => {
|
||||
visible.value = !visible.value;
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
getAd(255).then((data) => {
|
||||
assignFields(data);
|
||||
});
|
||||
};
|
||||
|
||||
reload();
|
||||
|
||||
const onClick = () => {
|
||||
emit('done', Number(props.index));
|
||||
};
|
||||
</script>
|
||||
<style scoped lang="less">
|
||||
.hidden-sm-and-down {
|
||||
:deep(.slick-slide) {
|
||||
overflow: hidden;
|
||||
}
|
||||
:deep(.slick-arrow.custom-slick-arrow) {
|
||||
font-size: 38px;
|
||||
}
|
||||
}
|
||||
.hidden-sm-and-up {
|
||||
:deep(.slick-arrow.custom-slick-arrow) {
|
||||
top: 50px;
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
.banner {
|
||||
:deep(.slick-arrow.custom-slick-arrow) {
|
||||
color: #fff;
|
||||
background-color: rgba(31, 45, 61, 0.11);
|
||||
transition: ease all 0.3s;
|
||||
opacity: 0.3;
|
||||
z-index: 1;
|
||||
}
|
||||
:deep(.slick-arrow.custom-slick-arrow:before) {
|
||||
display: none;
|
||||
}
|
||||
:deep(.slick-arrow.custom-slick-arrow:hover) {
|
||||
color: #fff;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
:deep(.slick-slide h3) {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.ad-item {
|
||||
position: relative;
|
||||
}
|
||||
.ad-text {
|
||||
display: none;
|
||||
top: 100px;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
width: 1100px;
|
||||
margin: 0 20vw;
|
||||
z-index: 999;
|
||||
.title {
|
||||
font-size: 50px;
|
||||
text-align: center;
|
||||
color: #ffffff;
|
||||
font-weight: bold;
|
||||
}
|
||||
.desc {
|
||||
color: #ffffff;
|
||||
text-align: center;
|
||||
}
|
||||
.btn {
|
||||
text-align: center;
|
||||
margin-top: 30px;
|
||||
}
|
||||
}
|
||||
.slick-dots {
|
||||
background-color: #ff0000;
|
||||
font-size: 800px;
|
||||
width: 1000px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
48
src/components/DesignHeader/index.vue
Normal file
48
src/components/DesignHeader/index.vue
Normal file
@@ -0,0 +1,48 @@
|
||||
<template>
|
||||
<div :class="item.name" :style="item.style" @mousedown="onClick">
|
||||
<div class="logo">
|
||||
<a-image
|
||||
src="https://oss.wsdns.cn/20240311/63b56fd176c14a5ca634254edefed4e3.png"
|
||||
:width="item.style"
|
||||
:preview="false"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { ref, watch } from 'vue';
|
||||
|
||||
const props = defineProps<{
|
||||
// 是否显示
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: any | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', item: any): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const item = ref<any>({
|
||||
body: { backgroundColor: '#ffffff' }
|
||||
});
|
||||
|
||||
const onClick = () => {
|
||||
// if (item.value.body.border) {
|
||||
// item.value.body.border = '';
|
||||
// } else {
|
||||
// item.value.body.border = '1px dashed #dddddd';
|
||||
// }
|
||||
emit('done', props.data);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.data,
|
||||
(data) => {
|
||||
item.value = data;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
<style scoped lang="less"></style>
|
||||
58
src/components/DictRadio/index.vue
Normal file
58
src/components/DictRadio/index.vue
Normal file
@@ -0,0 +1,58 @@
|
||||
<!-- 选择下拉框 -->
|
||||
<template>
|
||||
<a-radio-group
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@blur="onBlur"
|
||||
>
|
||||
<a-radio-button
|
||||
:value="item.value"
|
||||
v-for="(item, index) in data"
|
||||
:key="index"
|
||||
>
|
||||
{{ item.label }}
|
||||
</a-radio-button>
|
||||
</a-radio-group>
|
||||
<!-- <a-radio-group-->
|
||||
<!-- :value="value"-->
|
||||
<!-- :options="data"-->
|
||||
<!-- :placeholder="placeholder"-->
|
||||
<!-- @update:value="updateValue"-->
|
||||
<!-- @blur="onBlur"-->
|
||||
<!-- />-->
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
type?: any;
|
||||
placeholder?: string;
|
||||
dictCode?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择服务器厂商'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = getDictionaryOptions(props.dictCode);
|
||||
const content = ref<any>();
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = () => {
|
||||
emit('update:value', content.value);
|
||||
};
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
78
src/components/DictSelect/index.vue
Normal file
78
src/components/DictSelect/index.vue
Normal file
@@ -0,0 +1,78 @@
|
||||
<!-- 选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
:allow-clear="true"
|
||||
:show-search="true"
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
:style="`width: ${width}px`"
|
||||
@change="change"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { listDictData } from '@/api/system/dict-data';
|
||||
|
||||
const data = ref<any[]>([]);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'index', index: number): void;
|
||||
(e: 'blur'): void;
|
||||
(e: 'done', item: any): void;
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
dictCode?: string;
|
||||
showSearch?: string;
|
||||
allowClear?: boolean;
|
||||
width?: number;
|
||||
index?: number;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择服务器厂商'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
listDictData({ dictCode: props.dictCode }).then((res) => {
|
||||
data.value = res.map((d) => {
|
||||
return {
|
||||
dictDataId: d.dictDataId,
|
||||
dictDataCode: d.dictDataCode,
|
||||
dictDataName: d.dictDataName,
|
||||
key: d.dictDataCode,
|
||||
value: d.dictDataId,
|
||||
label: d.dictDataName,
|
||||
comments: d.comments
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
emit('index', Number(props.index));
|
||||
emit(
|
||||
'done',
|
||||
data.value.find((d) => d.value === value)
|
||||
);
|
||||
};
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
|
||||
const change = (e, item) => {
|
||||
emit('done', item);
|
||||
};
|
||||
</script>
|
||||
65
src/components/DictSelectMultiple/index.vue
Normal file
65
src/components/DictSelectMultiple/index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<a-select
|
||||
style="min-width: 100px"
|
||||
show-search
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
allow-clear
|
||||
:value="value"
|
||||
mode="multiple"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { listDictionaryData } from '@/api/system/dictionary-data';
|
||||
import type { SelectProps } from 'ant-design-vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
dictCode?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择服务器厂商'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = ref<SelectProps['options']>([]);
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
/* 获取字典数据 */
|
||||
listDictionaryData({
|
||||
dictCode: props.dictCode
|
||||
})
|
||||
.then((list) => {
|
||||
data.value = list.map((d) => {
|
||||
return {
|
||||
value: d.dictDataCode,
|
||||
label: d.dictDataName
|
||||
};
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
61
src/components/FormSelect/index.vue
Normal file
61
src/components/FormSelect/index.vue
Normal file
@@ -0,0 +1,61 @@
|
||||
<!-- 选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
allow-clear
|
||||
:show-search="true"
|
||||
optionFilterProp="label"
|
||||
:options="list"
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
:style="`width: ${width}px`"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { listForm } from '@/api/cms/form';
|
||||
import { ref } from 'vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'index', index: number): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
dictCode?: string;
|
||||
showSearch?: string;
|
||||
width?: number;
|
||||
index?: number;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择服务器厂商'
|
||||
}
|
||||
);
|
||||
|
||||
// 下拉数据
|
||||
const list = ref<any[]>([]);
|
||||
|
||||
listForm({}).then((data) => {
|
||||
list.value = data.map((d) => {
|
||||
return {
|
||||
label: d.name,
|
||||
value: d.formId
|
||||
};
|
||||
});
|
||||
});
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
emit('index', Number(props.index));
|
||||
};
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
127
src/components/IndustrySelect/index.vue
Normal file
127
src/components/IndustrySelect/index.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<!-- 省市区级联选择器 -->
|
||||
<template>
|
||||
<a-cascader
|
||||
:value="value"
|
||||
:options="regionsData"
|
||||
:show-search="showSearch"
|
||||
:placeholder="placeholder"
|
||||
dropdown-class-name="ele-pop-wrap-higher"
|
||||
@update:value="updateValue"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import type { ValueType } from 'ant-design-vue/es/vc-cascader/Cascader';
|
||||
import type { IndustryData } from './types';
|
||||
import { getIndustryData } from './load-data';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: string[];
|
||||
placeholder?: string;
|
||||
options?: IndustryData[];
|
||||
valueField?: 'label';
|
||||
type?: 'provinceCity' | 'province';
|
||||
showSearch?: boolean;
|
||||
}>(),
|
||||
{
|
||||
showSearch: true
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value?: string[]): void;
|
||||
(e: 'load-data-done', value: IndustryData[]): void;
|
||||
}>();
|
||||
|
||||
// 级联选择器数据
|
||||
const regionsData = ref<IndustryData[]>([]);
|
||||
|
||||
/* 更新 value */
|
||||
const updateValue = (value: ValueType) => {
|
||||
emit('update:value', value as string[]);
|
||||
};
|
||||
|
||||
/* 级联选择器数据 value 处理 */
|
||||
const formatData = (data: IndustryData[]) => {
|
||||
if (props.valueField === 'label') {
|
||||
return data.map((d) => {
|
||||
const item: IndustryData = {
|
||||
label: d.label,
|
||||
value: d.label
|
||||
};
|
||||
if (d.children) {
|
||||
item.children = d.children.map((c) => {
|
||||
const cItem: IndustryData = {
|
||||
label: c.label,
|
||||
value: c.label
|
||||
};
|
||||
if (c.children) {
|
||||
cItem.children = c.children.map((cc) => {
|
||||
return {
|
||||
label: cc.label,
|
||||
value: cc.label
|
||||
};
|
||||
});
|
||||
}
|
||||
return cItem;
|
||||
});
|
||||
}
|
||||
return item;
|
||||
});
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
/* 省市区数据筛选 */
|
||||
const filterData = (data: IndustryData[]) => {
|
||||
if (props.type === 'provinceCity') {
|
||||
return formatData(
|
||||
data.map((d) => {
|
||||
const item: IndustryData = {
|
||||
label: d.label,
|
||||
value: d.value
|
||||
};
|
||||
if (d.children) {
|
||||
item.children = d.children.map((c) => {
|
||||
return {
|
||||
label: c.label,
|
||||
value: c.value
|
||||
};
|
||||
});
|
||||
}
|
||||
return item;
|
||||
})
|
||||
);
|
||||
} else if (props.type === 'province') {
|
||||
return formatData(
|
||||
data.map((d) => {
|
||||
return {
|
||||
label: d.label,
|
||||
value: d.value
|
||||
};
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return formatData(data);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.options,
|
||||
(options) => {
|
||||
regionsData.value = filterData(options ?? []);
|
||||
if (!options) {
|
||||
getIndustryData().then((data) => {
|
||||
regionsData.value = filterData(data ?? []);
|
||||
emit('load-data-done', data);
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
</script>
|
||||
25
src/components/IndustrySelect/load-data.ts
Normal file
25
src/components/IndustrySelect/load-data.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import request from '@/utils/request';
|
||||
import type { IndustryData } from './types';
|
||||
const BASE_URL = import.meta.env.BASE_URL;
|
||||
let reqPromise: Promise<IndustryData[]>;
|
||||
|
||||
/**
|
||||
* 获取省市区数据
|
||||
*/
|
||||
export function getIndustryData() {
|
||||
if (!reqPromise) {
|
||||
reqPromise = new Promise<IndustryData[]>((resolve, reject) => {
|
||||
request
|
||||
.get<IndustryData[]>(BASE_URL + 'json/industry-data.json', {
|
||||
baseURL: ''
|
||||
})
|
||||
.then((res) => {
|
||||
resolve(res.data ?? []);
|
||||
})
|
||||
.catch((e) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
return reqPromise;
|
||||
}
|
||||
15
src/components/IndustrySelect/types/index.ts
Normal file
15
src/components/IndustrySelect/types/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 行业类型
|
||||
*/
|
||||
export interface IndustryData {
|
||||
label: string;
|
||||
value: string;
|
||||
children?: {
|
||||
value: string;
|
||||
label: string;
|
||||
children?: {
|
||||
value: string;
|
||||
label: string;
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
76
src/components/PayMethod/index.vue
Normal file
76
src/components/PayMethod/index.vue
Normal file
@@ -0,0 +1,76 @@
|
||||
<!-- 选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
:allow-clear="true"
|
||||
:show-search="true"
|
||||
optionFilterProp="label"
|
||||
:options="options"
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
:style="`width: 200px`"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { SelectProps } from 'ant-design-vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string, item: any): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
type?: any;
|
||||
placeholder?: string;
|
||||
dictCode?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择支付方式'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const options = ref<SelectProps['options']>([
|
||||
{
|
||||
value: 0,
|
||||
label: '余额支付',
|
||||
key: 'balancePay',
|
||||
icon: 'PayCircleOutlined'
|
||||
},
|
||||
{ value: 1, label: '微信支付', key: 'wxPay', icon: 'WechatOutlined' },
|
||||
{
|
||||
value: 2,
|
||||
label: '会员卡支付',
|
||||
key: 'userCardPay',
|
||||
icon: 'IdcardOutlined'
|
||||
},
|
||||
{
|
||||
value: 3,
|
||||
label: '支付宝支付',
|
||||
key: 'aliPay',
|
||||
icon: 'AlipayCircleOutlined'
|
||||
},
|
||||
{
|
||||
value: 4,
|
||||
label: '现金支付',
|
||||
key: 'cashPayment',
|
||||
icon: 'PayCircleOutlined'
|
||||
}
|
||||
]);
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
const item = options.value?.find((d) => d.value == value);
|
||||
console.log(item);
|
||||
emit('update:value', value, item);
|
||||
};
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
39
src/components/QrCode/index.vue
Normal file
39
src/components/QrCode/index.vue
Normal file
@@ -0,0 +1,39 @@
|
||||
<template>
|
||||
<a-modal
|
||||
:width="450"
|
||||
:visible="visible"
|
||||
:title="title || '分享二维码'"
|
||||
:maskClosable="false"
|
||||
@cancel="save"
|
||||
@ok="save"
|
||||
>
|
||||
<div class="flex p-3 flex-col justify-center items-center">
|
||||
<ele-qr-code-svg :value="`${data}`" :size="260" />
|
||||
<span class="text-gray-400 py-3" @click="copyText(data)">{{ data }}</span>
|
||||
<span class="text-gray-500 text-lg"> 使用手机扫一扫 </span>
|
||||
</div>
|
||||
</a-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { CmsArticle } from '@/api/cms/cmsArticle/model';
|
||||
import { copyText } from '@/utils/common';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
title: String | undefined;
|
||||
data: CmsArticle;
|
||||
}>(),
|
||||
{}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
}>();
|
||||
|
||||
const save = () => {
|
||||
emit('done');
|
||||
};
|
||||
</script>
|
||||
259
src/components/QrLogin/demo.vue
Normal file
259
src/components/QrLogin/demo.vue
Normal file
@@ -0,0 +1,259 @@
|
||||
<template>
|
||||
<div class="qr-login-demo">
|
||||
<a-card title="二维码登录演示" :bordered="false">
|
||||
<div class="demo-content">
|
||||
<!-- 二维码登录组件 -->
|
||||
<QrLogin
|
||||
@loginSuccess="handleLoginSuccess"
|
||||
@loginError="handleLoginError"
|
||||
/>
|
||||
|
||||
<!-- 演示控制面板 -->
|
||||
<div class="demo-controls">
|
||||
<h4>演示控制</h4>
|
||||
<a-space direction="vertical" style="width: 100%">
|
||||
<a-button @click="simulateScanned" type="primary" ghost>
|
||||
模拟扫码
|
||||
</a-button>
|
||||
<a-button @click="simulateConfirmed" type="primary">
|
||||
模拟确认登录
|
||||
</a-button>
|
||||
<a-button @click="simulateExpired" type="default">
|
||||
模拟过期
|
||||
</a-button>
|
||||
<a-button @click="simulateError" danger> 模拟错误 </a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
|
||||
<!-- 状态显示 -->
|
||||
<div class="demo-status">
|
||||
<h4>当前状态</h4>
|
||||
<a-descriptions :column="1" size="small">
|
||||
<a-descriptions-item label="二维码Key">
|
||||
{{ currentQrKey || '未生成' }}
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="状态">
|
||||
<a-tag :color="getStatusColor(currentStatus)">
|
||||
{{ getStatusText(currentStatus) }}
|
||||
</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item label="剩余时间">
|
||||
{{ remainingTime }}秒
|
||||
</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<!-- 日志显示 -->
|
||||
<a-card title="操作日志" :bordered="false" style="margin-top: 16px">
|
||||
<div class="demo-logs">
|
||||
<div
|
||||
v-for="(log, index) in logs"
|
||||
:key="index"
|
||||
class="log-item"
|
||||
:class="log.type"
|
||||
>
|
||||
<span class="log-time">{{ log.time }}</span>
|
||||
<span class="log-message">{{ log.message }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import QrLogin from './index.vue';
|
||||
|
||||
// 响应式数据
|
||||
const currentQrKey = ref<string>('');
|
||||
const currentStatus = ref<string>('loading');
|
||||
const remainingTime = ref<number>(300);
|
||||
const logs = ref<Array<{ time: string; message: string; type: string }>>([]);
|
||||
|
||||
// 添加日志
|
||||
const addLog = (
|
||||
message: string,
|
||||
type: 'info' | 'success' | 'error' | 'warning' = 'info'
|
||||
) => {
|
||||
const now = new Date();
|
||||
const time = now.toLocaleTimeString();
|
||||
logs.value.unshift({ time, message, type });
|
||||
|
||||
// 限制日志数量
|
||||
if (logs.value.length > 50) {
|
||||
logs.value = logs.value.slice(0, 50);
|
||||
}
|
||||
};
|
||||
|
||||
// 获取状态颜色
|
||||
const getStatusColor = (status: string) => {
|
||||
const colors = {
|
||||
loading: 'blue',
|
||||
active: 'green',
|
||||
scanned: 'orange',
|
||||
expired: 'red',
|
||||
error: 'red'
|
||||
};
|
||||
return colors[status] || 'default';
|
||||
};
|
||||
|
||||
// 获取状态文本
|
||||
const getStatusText = (status: string) => {
|
||||
const texts = {
|
||||
loading: '正在生成',
|
||||
active: '等待扫码',
|
||||
scanned: '已扫码',
|
||||
expired: '已过期',
|
||||
error: '生成失败'
|
||||
};
|
||||
return texts[status] || status;
|
||||
};
|
||||
|
||||
// 处理登录成功
|
||||
const handleLoginSuccess = (token: string) => {
|
||||
addLog(`登录成功,获得token: ${token.substring(0, 20)}...`, 'success');
|
||||
message.success('二维码登录成功!');
|
||||
};
|
||||
|
||||
// 处理登录错误
|
||||
const handleLoginError = (error: string) => {
|
||||
addLog(`登录失败: ${error}`, 'error');
|
||||
message.error(`登录失败: ${error}`);
|
||||
};
|
||||
|
||||
// 模拟扫码
|
||||
const simulateScanned = () => {
|
||||
currentStatus.value = 'scanned';
|
||||
addLog('模拟用户扫码', 'info');
|
||||
message.info('模拟扫码成功');
|
||||
};
|
||||
|
||||
// 模拟确认登录
|
||||
const simulateConfirmed = () => {
|
||||
const mockToken = 'mock_token_' + Date.now();
|
||||
handleLoginSuccess(mockToken);
|
||||
};
|
||||
|
||||
// 模拟过期
|
||||
const simulateExpired = () => {
|
||||
currentStatus.value = 'expired';
|
||||
remainingTime.value = 0;
|
||||
addLog('模拟二维码过期', 'warning');
|
||||
message.warning('二维码已过期');
|
||||
};
|
||||
|
||||
// 模拟错误
|
||||
const simulateError = () => {
|
||||
currentStatus.value = 'error';
|
||||
addLog('模拟生成二维码失败', 'error');
|
||||
handleLoginError('网络连接失败');
|
||||
};
|
||||
|
||||
// 组件挂载
|
||||
onMounted(() => {
|
||||
addLog('二维码登录演示组件已加载', 'info');
|
||||
|
||||
// 模拟生成二维码
|
||||
setTimeout(() => {
|
||||
currentQrKey.value = 'demo_qr_' + Date.now();
|
||||
currentStatus.value = 'active';
|
||||
addLog(`生成二维码成功,Key: ${currentQrKey.value}`, 'success');
|
||||
|
||||
// 开始倒计时
|
||||
const timer = setInterval(() => {
|
||||
if (remainingTime.value > 0) {
|
||||
remainingTime.value--;
|
||||
} else {
|
||||
clearInterval(timer);
|
||||
if (currentStatus.value === 'active') {
|
||||
simulateExpired();
|
||||
}
|
||||
}
|
||||
}, 1000);
|
||||
}, 1000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.qr-login-demo {
|
||||
max-width: 800px;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
}
|
||||
|
||||
.demo-content {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 200px 200px;
|
||||
gap: 24px;
|
||||
align-items: start;
|
||||
}
|
||||
|
||||
.demo-controls {
|
||||
h4 {
|
||||
margin-bottom: 12px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-status {
|
||||
h4 {
|
||||
margin-bottom: 12px;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.demo-logs {
|
||||
max-height: 300px;
|
||||
overflow-y: auto;
|
||||
background: #f8f9fa;
|
||||
border-radius: 4px;
|
||||
padding: 12px;
|
||||
}
|
||||
|
||||
.log-item {
|
||||
display: flex;
|
||||
margin-bottom: 8px;
|
||||
font-size: 12px;
|
||||
line-height: 1.4;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.log-time {
|
||||
color: #999;
|
||||
margin-right: 8px;
|
||||
min-width: 80px;
|
||||
}
|
||||
|
||||
.log-message {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
&.info .log-message {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
&.success .log-message {
|
||||
color: #52c41a;
|
||||
}
|
||||
|
||||
&.error .log-message {
|
||||
color: #ff4d4f;
|
||||
}
|
||||
|
||||
&.warning .log-message {
|
||||
color: #faad14;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.demo-content {
|
||||
grid-template-columns: 1fr;
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
325
src/components/QrLogin/index.vue
Normal file
325
src/components/QrLogin/index.vue
Normal file
@@ -0,0 +1,325 @@
|
||||
<template>
|
||||
<div class="qr-login-container">
|
||||
<div class="qr-code-wrapper">
|
||||
<!-- 二维码显示区域 -->
|
||||
<div v-if="qrCodeStatus === 'loading'" class="qr-loading">
|
||||
<a-spin size="large" />
|
||||
<p class="loading-text">正在生成二维码...</p>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-else-if="qrCodeStatus === 'active'"
|
||||
class="qr-active cursor-pointer"
|
||||
>
|
||||
<ele-qr-code-svg
|
||||
:value="qrCodeData"
|
||||
:size="200"
|
||||
@click="refreshQrCode"
|
||||
/>
|
||||
<p class="qr-tip">请使用手机APP或小程序扫码登录</p>
|
||||
<p class="qr-expire-tip">二维码有效期:{{ formatTime(expireTime) }}</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="qrCodeStatus === 'scanned'" class="qr-scanned">
|
||||
<div class="scanned-icon">
|
||||
<CheckCircleOutlined style="font-size: 48px; color: #52c41a" />
|
||||
</div>
|
||||
<p class="scanned-text">扫码成功,请在手机上确认登录</p>
|
||||
</div>
|
||||
|
||||
<div v-else-if="qrCodeStatus === 'expired'" class="qr-expired">
|
||||
<div class="expired-icon">
|
||||
<ClockCircleOutlined style="font-size: 48px; color: #ff4d4f" />
|
||||
</div>
|
||||
<p class="expired-text">二维码已过期</p>
|
||||
<a-button type="primary" @click="refreshQrCode">刷新二维码</a-button>
|
||||
</div>
|
||||
|
||||
<div v-else-if="qrCodeStatus === 'error'" class="qr-error">
|
||||
<div class="error-icon">
|
||||
<ExclamationCircleOutlined style="font-size: 48px; color: #ff4d4f" />
|
||||
</div>
|
||||
<p class="error-text">{{ errorMessage || '生成二维码失败' }}</p>
|
||||
<a-button type="primary" @click="refreshQrCode">重新生成</a-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 刷新按钮 -->
|
||||
<div class="qr-actions" v-if="qrCodeStatus === 'active'">
|
||||
<a-button type="link" @click="refreshQrCode" :loading="refreshing">
|
||||
<ReloadOutlined /> 刷新二维码
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted, computed } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import {
|
||||
CheckCircleOutlined,
|
||||
ClockCircleOutlined,
|
||||
ExclamationCircleOutlined,
|
||||
ReloadOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import {
|
||||
generateQrCode,
|
||||
checkQrCodeStatus,
|
||||
type QrCodeResponse,
|
||||
QrCodeStatusResponse
|
||||
} from '@/api/passport/qrLogin';
|
||||
|
||||
// 定义组件事件
|
||||
const emit = defineEmits<{
|
||||
(e: 'loginSuccess', data: QrCodeStatusResponse): void;
|
||||
(e: 'loginError', error: string): void;
|
||||
}>();
|
||||
|
||||
// 响应式数据
|
||||
const qrCodeData = ref<string>('');
|
||||
const qrCodeToken = ref<string>('');
|
||||
const qrCodeStatus = ref<
|
||||
'loading' | 'active' | 'scanned' | 'expired' | 'error'
|
||||
>('loading');
|
||||
const expireTime = ref<number>(0);
|
||||
const errorMessage = ref<string>('');
|
||||
const refreshing = ref<boolean>(false);
|
||||
|
||||
// 定时器
|
||||
let statusCheckTimer: number | null = null;
|
||||
let expireTimer: number | null = null;
|
||||
|
||||
// 计算属性:格式化剩余时间
|
||||
const formatTime = computed(() => {
|
||||
return (seconds: number) => {
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const remainingSeconds = seconds % 60;
|
||||
return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* 生成二维码
|
||||
*/
|
||||
const generateQrCodeData = async () => {
|
||||
try {
|
||||
qrCodeStatus.value = 'loading';
|
||||
const response: QrCodeResponse = await generateQrCode();
|
||||
|
||||
// 后端返回的qrCode是二维码内容,我们需要构造完整的URL
|
||||
const baseUrl = window.location.origin;
|
||||
const qrCodeUrl = `${baseUrl}/qr-confirm?qrCodeKey=${response.token}`;
|
||||
|
||||
qrCodeData.value = qrCodeUrl;
|
||||
qrCodeToken.value = response.token;
|
||||
qrCodeStatus.value = 'active';
|
||||
expireTime.value = response.expiresIn || 300; // 默认5分钟过期
|
||||
|
||||
// 开始检查二维码状态
|
||||
startStatusCheck();
|
||||
// 开始倒计时
|
||||
startExpireCountdown();
|
||||
} catch (error: any) {
|
||||
qrCodeStatus.value = 'error';
|
||||
errorMessage.value = error.message || '生成二维码失败';
|
||||
message.error(errorMessage.value);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 开始检查二维码状态
|
||||
*/
|
||||
const startStatusCheck = () => {
|
||||
if (statusCheckTimer) {
|
||||
clearInterval(statusCheckTimer);
|
||||
}
|
||||
|
||||
statusCheckTimer = window.setInterval(async () => {
|
||||
try {
|
||||
const status = await checkQrCodeStatus(qrCodeToken.value);
|
||||
|
||||
switch (status.status) {
|
||||
case 'scanned':
|
||||
qrCodeStatus.value = 'scanned';
|
||||
break;
|
||||
case 'confirmed':
|
||||
// 登录成功
|
||||
if (status.tenantId) {
|
||||
localStorage.setItem('TenantId', `${status.tenantId}`);
|
||||
}
|
||||
qrCodeStatus.value = 'active';
|
||||
stopAllTimers();
|
||||
emit('loginSuccess', status);
|
||||
break;
|
||||
case 'expired':
|
||||
qrCodeStatus.value = 'expired';
|
||||
stopAllTimers();
|
||||
break;
|
||||
case 'pending':
|
||||
// 继续等待
|
||||
break;
|
||||
}
|
||||
|
||||
// 更新剩余时间
|
||||
if (status.expiresIn !== undefined) {
|
||||
expireTime.value = status.expiresIn;
|
||||
}
|
||||
} catch (error: any) {
|
||||
console.error('检查二维码状态失败:', error);
|
||||
// 继续检查,不中断流程
|
||||
}
|
||||
}, 2000); // 每2秒检查一次
|
||||
};
|
||||
|
||||
/**
|
||||
* 开始过期倒计时
|
||||
*/
|
||||
const startExpireCountdown = () => {
|
||||
if (expireTimer) {
|
||||
clearInterval(expireTimer);
|
||||
}
|
||||
|
||||
expireTimer = window.setInterval(() => {
|
||||
if (expireTime.value <= 0) {
|
||||
qrCodeStatus.value = 'expired';
|
||||
stopAllTimers();
|
||||
return;
|
||||
}
|
||||
expireTime.value--;
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
/**
|
||||
* 停止所有定时器
|
||||
*/
|
||||
const stopAllTimers = () => {
|
||||
if (statusCheckTimer) {
|
||||
clearInterval(statusCheckTimer);
|
||||
statusCheckTimer = null;
|
||||
}
|
||||
if (expireTimer) {
|
||||
clearInterval(expireTimer);
|
||||
expireTimer = null;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* 刷新二维码
|
||||
*/
|
||||
const refreshQrCode = async () => {
|
||||
refreshing.value = true;
|
||||
stopAllTimers();
|
||||
|
||||
try {
|
||||
await generateQrCodeData();
|
||||
} finally {
|
||||
refreshing.value = false;
|
||||
}
|
||||
};
|
||||
|
||||
// 组件挂载时生成二维码
|
||||
onMounted(() => {
|
||||
generateQrCodeData();
|
||||
});
|
||||
|
||||
// 组件卸载时清理定时器
|
||||
onUnmounted(() => {
|
||||
stopAllTimers();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.qr-login-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 20px;
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
.qr-code-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 250px;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.qr-loading {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.loading-text {
|
||||
color: #666;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.qr-active {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 12px;
|
||||
|
||||
.qr-tip {
|
||||
color: #333;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.qr-expire-tip {
|
||||
color: #999;
|
||||
font-size: 12px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.qr-scanned {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.scanned-text {
|
||||
color: #52c41a;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.qr-expired {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.expired-text {
|
||||
color: #ff4d4f;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.qr-error {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 16px;
|
||||
|
||||
.error-text {
|
||||
color: #ff4d4f;
|
||||
font-size: 14px;
|
||||
margin: 0;
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.qr-actions {
|
||||
margin-top: 16px;
|
||||
}
|
||||
</style>
|
||||
45
src/components/RadioGroup/index.vue
Normal file
45
src/components/RadioGroup/index.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<!-- 选择下拉框 -->
|
||||
<template>
|
||||
<a-radio-group
|
||||
:value="value"
|
||||
:options="data"
|
||||
:option-type="type"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
@blur="onBlur"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
dictCode?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择服务器厂商'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = getDictionaryOptions(props.dictCode);
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
console.log(value);
|
||||
emit('update:value', value);
|
||||
};
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
21
src/components/RedirectLayout/index.ts
Normal file
21
src/components/RedirectLayout/index.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
/** 用于刷新的路由组件 */
|
||||
import { defineComponent, unref, h } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { setRouteReload } from '@/utils/page-tab-util';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RedirectLayout',
|
||||
setup() {
|
||||
const { currentRoute, replace } = useRouter();
|
||||
const { params, query } = unref(currentRoute);
|
||||
const from = Array.isArray(params.path)
|
||||
? params.path.join('/')
|
||||
: params.path;
|
||||
const path = '/' + from;
|
||||
setTimeout(() => {
|
||||
setRouteReload(null);
|
||||
replace({ path, query });
|
||||
}, 100);
|
||||
return () => h('div');
|
||||
}
|
||||
});
|
||||
127
src/components/RegionsSelect/index.vue
Normal file
127
src/components/RegionsSelect/index.vue
Normal file
@@ -0,0 +1,127 @@
|
||||
<!-- 省市区级联选择器 -->
|
||||
<template>
|
||||
<a-cascader
|
||||
:value="value"
|
||||
:options="regionsData"
|
||||
:show-search="showSearch"
|
||||
:placeholder="placeholder"
|
||||
dropdown-class-name="ele-pop-wrap-higher"
|
||||
@update:value="updateValue"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import type { ValueType } from 'ant-design-vue/es/vc-cascader/Cascader';
|
||||
import type { RegionsData } from './types';
|
||||
import { getRegionsData } from './load-data';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: string[];
|
||||
placeholder?: string;
|
||||
options?: RegionsData[];
|
||||
valueField?: 'label';
|
||||
type?: 'provinceCity' | 'province';
|
||||
showSearch?: boolean;
|
||||
}>(),
|
||||
{
|
||||
showSearch: true
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value?: string[]): void;
|
||||
(e: 'load-data-done', value: RegionsData[]): void;
|
||||
}>();
|
||||
|
||||
// 级联选择器数据
|
||||
const regionsData = ref<RegionsData[]>([]);
|
||||
|
||||
/* 更新 value */
|
||||
const updateValue = (value: ValueType) => {
|
||||
emit('update:value', value as string[]);
|
||||
};
|
||||
|
||||
/* 级联选择器数据 value 处理 */
|
||||
const formatData = (data: RegionsData[]) => {
|
||||
if (props.valueField === 'label') {
|
||||
return data.map((d) => {
|
||||
const item: RegionsData = {
|
||||
label: d.label,
|
||||
value: d.label
|
||||
};
|
||||
if (d.children) {
|
||||
item.children = d.children.map((c) => {
|
||||
const cItem: RegionsData = {
|
||||
label: c.label,
|
||||
value: c.label
|
||||
};
|
||||
if (c.children) {
|
||||
cItem.children = c.children.map((cc) => {
|
||||
return {
|
||||
label: cc.label,
|
||||
value: cc.label
|
||||
};
|
||||
});
|
||||
}
|
||||
return cItem;
|
||||
});
|
||||
}
|
||||
return item;
|
||||
});
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
/* 省市区数据筛选 */
|
||||
const filterData = (data: RegionsData[]) => {
|
||||
if (props.type === 'provinceCity') {
|
||||
return formatData(
|
||||
data.map((d) => {
|
||||
const item: RegionsData = {
|
||||
label: d.label,
|
||||
value: d.value
|
||||
};
|
||||
if (d.children) {
|
||||
item.children = d.children.map((c) => {
|
||||
return {
|
||||
label: c.label,
|
||||
value: c.value
|
||||
};
|
||||
});
|
||||
}
|
||||
return item;
|
||||
})
|
||||
);
|
||||
} else if (props.type === 'province') {
|
||||
return formatData(
|
||||
data.map((d) => {
|
||||
return {
|
||||
label: d.label,
|
||||
value: d.value
|
||||
};
|
||||
})
|
||||
);
|
||||
} else {
|
||||
return formatData(data);
|
||||
}
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.options,
|
||||
(options) => {
|
||||
regionsData.value = filterData(options ?? []);
|
||||
if (!options) {
|
||||
getRegionsData().then((data) => {
|
||||
regionsData.value = filterData(data ?? []);
|
||||
emit('load-data-done', data);
|
||||
});
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
</script>
|
||||
25
src/components/RegionsSelect/load-data.ts
Normal file
25
src/components/RegionsSelect/load-data.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import request from '@/utils/request';
|
||||
import type { RegionsData } from './types';
|
||||
const BASE_URL = import.meta.env.BASE_URL;
|
||||
let reqPromise: Promise<RegionsData[]>;
|
||||
|
||||
/**
|
||||
* 获取省市区数据
|
||||
*/
|
||||
export function getRegionsData() {
|
||||
if (!reqPromise) {
|
||||
reqPromise = new Promise<RegionsData[]>((resolve, reject) => {
|
||||
request
|
||||
.get<RegionsData[]>(BASE_URL + 'json/regions-data.json', {
|
||||
baseURL: ''
|
||||
})
|
||||
.then((res) => {
|
||||
resolve(res.data ?? []);
|
||||
})
|
||||
.catch((e) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
return reqPromise;
|
||||
}
|
||||
15
src/components/RegionsSelect/types/index.ts
Normal file
15
src/components/RegionsSelect/types/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 省市区数据类型
|
||||
*/
|
||||
export interface RegionsData {
|
||||
label: string;
|
||||
value: string;
|
||||
children?: {
|
||||
value: string;
|
||||
label: string;
|
||||
children?: {
|
||||
value: string;
|
||||
label: string;
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
26
src/components/RouterLayout/index.vue
Normal file
26
src/components/RouterLayout/index.vue
Normal file
@@ -0,0 +1,26 @@
|
||||
<!-- router-view 结合 keep-alive 组件 -->
|
||||
<template>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition :name="transitionName" mode="out-in" appear>
|
||||
<keep-alive :include="keepAliveInclude">
|
||||
<component :is="Component" />
|
||||
</keep-alive>
|
||||
</transition>
|
||||
</router-view>
|
||||
</template>
|
||||
|
||||
<script lang="ts">
|
||||
import { defineComponent } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
|
||||
export default defineComponent({
|
||||
name: 'RouterLayout',
|
||||
setup() {
|
||||
const themeStore = useThemeStore();
|
||||
const { keepAliveInclude, transitionName } = storeToRefs(themeStore);
|
||||
|
||||
return { keepAliveInclude, transitionName };
|
||||
}
|
||||
});
|
||||
</script>
|
||||
143
src/components/SelectArticle/components/select-data.vue
Normal file
143
src/components/SelectArticle/components/select-data.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="articleId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<template v-if="pageId == record.articleId">
|
||||
<a-radio v-model:checked="checked" @click="onRadio(record)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-radio @click="onRadio(record)" />
|
||||
<span class="ele-text-secondary">选择</span>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import { Article } from '@/api/cms/article/model';
|
||||
import { pageArticle } from '@/api/cms/article';
|
||||
import { ArticleParam } from '@/api/cms/article/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: Article | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: Article): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
const pageId = ref<number>(0);
|
||||
const checked = ref<boolean>(true);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '操作',
|
||||
width: 180,
|
||||
key: 'action'
|
||||
},
|
||||
{
|
||||
title: '文章ID',
|
||||
dataIndex: 'articleId',
|
||||
key: 'articleId'
|
||||
},
|
||||
{
|
||||
title: '文章标题',
|
||||
dataIndex: 'title',
|
||||
key: 'title'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.title = searchText.value;
|
||||
}
|
||||
return pageArticle({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ArticleParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const onRadio = (record: Article) => {
|
||||
pageId.value = Number(record.articleId);
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Article) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
// onClick: () => {
|
||||
// updateVisible(false);
|
||||
// emit('done', record);
|
||||
// },
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
60
src/components/SelectArticle/index.vue
Normal file
60
src/components/SelectArticle/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { CmsDesign } from '@/api/cms/cmsDesign/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择数据'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', CmsDesign): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<CmsDesign | null>(null);
|
||||
const content = ref<any>(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: CmsDesign) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('done', content.value);
|
||||
};
|
||||
</script>
|
||||
143
src/components/SelectArticleCategory/components/select-data.vue
Normal file
143
src/components/SelectArticleCategory/components/select-data.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="categoryId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<template v-if="pageId == record.categoryId">
|
||||
<a-radio v-model:checked="checked" @click="onRadio(record)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-radio @click="onRadio(record)" />
|
||||
<span class="ele-text-secondary">选择</span>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import { ArticleCategory } from '@/api/cms/category/model';
|
||||
import { pageArticleCategory } from '@/api/cms/category';
|
||||
import { ArticleCategoryParam } from '@/api/cms/category/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: ArticleCategory | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: ArticleCategory): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
const pageId = ref<number>(0);
|
||||
const checked = ref<boolean>(true);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '操作',
|
||||
width: 180,
|
||||
key: 'action'
|
||||
},
|
||||
{
|
||||
title: '分类ID',
|
||||
dataIndex: 'categoryId',
|
||||
key: 'categoryId'
|
||||
},
|
||||
{
|
||||
title: '分类名称',
|
||||
dataIndex: 'title',
|
||||
key: 'title'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.title = searchText.value;
|
||||
}
|
||||
return pageArticleCategory({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ArticleCategoryParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const onRadio = (record: ArticleCategory) => {
|
||||
pageId.value = Number(record.pageId);
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ArticleCategory) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
// onClick: () => {
|
||||
// updateVisible(false);
|
||||
// emit('done', record);
|
||||
// },
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
60
src/components/SelectArticleCategory/index.vue
Normal file
60
src/components/SelectArticleCategory/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { CmsDesign } from '@/api/cms/Cmsdesign/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择数据'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', Design): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<CmsDesign | null>(null);
|
||||
const content = ref<any>(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: CmsDesign) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('done', content.value);
|
||||
};
|
||||
</script>
|
||||
144
src/components/SelectDesign/components/select-data.vue
Normal file
144
src/components/SelectDesign/components/select-data.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="pageId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<div class="cursor-pointer" @click="onRadio(record)">{{
|
||||
record.name
|
||||
}}</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<template v-if="pageId == record.pageId">
|
||||
<a-radio v-model:checked="checked" @click="onRadio(record)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<div class="cursor-pointer" @click="onRadio(record)">
|
||||
<a-radio />
|
||||
<span class="text-blue-600 cursor-pointer">选择</span>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { pageCmsDesign } from '@/api/cms/CmsDesign';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import { CmsDesign, CmsDesignParam } from '@/api/cms/cmsDesign/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: CmsDesign | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: CmsDesign): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
const pageId = ref<number>(0);
|
||||
const checked = ref<boolean>(true);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '页面名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.name = searchText.value;
|
||||
}
|
||||
return pageCmsDesign({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: CmsDesignParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const onRadio = (record: CmsDesign) => {
|
||||
pageId.value = Number(record.pageId);
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: CmsDesign) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
// onClick: () => {
|
||||
// updateVisible(false);
|
||||
// emit('done', record);
|
||||
// },
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
60
src/components/SelectDesign/index.vue
Normal file
60
src/components/SelectDesign/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { CmsDesign } from '@/api/cms/cmsDesign/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择数据'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', Design): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<CmsDesign | null>(null);
|
||||
const content = ref<any>(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: CmsDesign) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('done', content.value);
|
||||
};
|
||||
</script>
|
||||
146
src/components/SelectDict/components/select-data.vue
Normal file
146
src/components/SelectDict/components/select-data.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="dictDataId"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:datasource="datasource"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="where.keywords"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { pageDictData } from '@/api/system/dict-data';
|
||||
import { DictData, DictDataParam } from '@/api/system/dict-data/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
title?: any;
|
||||
// 修改回显的数据
|
||||
data?: DictData | null;
|
||||
dictCode?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: DictData): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单数据
|
||||
const { where } = useSearch<DictDataParam>({
|
||||
dictCode: undefined,
|
||||
keywords: ''
|
||||
});
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'dictDataId',
|
||||
key: 'dictDataId'
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'dictDataName',
|
||||
key: 'dictDataName'
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'comments',
|
||||
key: 'comments'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where.dictCode = props.dictCode;
|
||||
where.tenantId = 5;
|
||||
return pageDictData({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = () => {
|
||||
// selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: DictData) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.app-box {
|
||||
display: flex;
|
||||
.app-info {
|
||||
display: flex;
|
||||
margin-left: 15px;
|
||||
margin-right: 15px;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
67
src/components/SelectDict/index.vue
Normal file
67
src/components/SelectDict/index.vue
Normal file
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<select-data
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:dictCode="dictCode"
|
||||
:title="placeholder"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { Dict } from '@/api/system/dict/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
index?: number;
|
||||
dictCode?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择字典'
|
||||
}
|
||||
);
|
||||
|
||||
const content = ref<any>(props.value);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', Dict): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<Dict | null>(null);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Dict) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = (row) => {
|
||||
row.index = Number(props.index);
|
||||
emit('done', row);
|
||||
};
|
||||
// 查询租户列表
|
||||
// const appList = ref<App[] | undefined>([]);
|
||||
</script>
|
||||
151
src/components/SelectDictDictionary/components/select-data.vue
Normal file
151
src/components/SelectDictDictionary/components/select-data.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="dictDataId"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:datasource="datasource"
|
||||
tool-class="ele-toolbar-form"
|
||||
class="sys-org-table"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="where.keywords"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'dictDataName'">
|
||||
<a-tag class="cursor-pointer">{{ record.dictDataName }}</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'comments'">
|
||||
<span class="text-gray-300">{{ record.comments }}</span>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { DictData, DictDataParam } from '@/api/system/dict-data/model';
|
||||
import { pageDictionaryData } from '@/api/system/dictionary-data';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
title?: any;
|
||||
// 修改回显的数据
|
||||
data?: DictData | null;
|
||||
dictCode?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: DictData): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单数据
|
||||
const { where } = useSearch<DictDataParam>({
|
||||
dictCode: undefined,
|
||||
keywords: ''
|
||||
});
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'dictDataId',
|
||||
key: 'dictDataId'
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'dictDataName',
|
||||
key: 'dictDataName'
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'comments',
|
||||
key: 'comments'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where.dictCode = props.dictCode;
|
||||
return pageDictionaryData({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = () => {
|
||||
// selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: DictData) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less">
|
||||
.app-box {
|
||||
display: flex;
|
||||
.app-info {
|
||||
display: flex;
|
||||
margin-left: 15px;
|
||||
margin-right: 15px;
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
66
src/components/SelectDictDictionary/index.vue
Normal file
66
src/components/SelectDictDictionary/index.vue
Normal file
@@ -0,0 +1,66 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<select-data
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:dictCode="dictCode"
|
||||
:title="placeholder"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { Dict } from '@/api/system/dict/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
index?: number;
|
||||
dictCode?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择字典'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', Dict): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<Dict | null>(null);
|
||||
const content = ref<any>(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Dict) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = (row) => {
|
||||
row.index = Number(props.index);
|
||||
emit('done', content.value);
|
||||
};
|
||||
// 查询租户列表
|
||||
// const appList = ref<App[] | undefined>([]);
|
||||
</script>
|
||||
139
src/components/SelectFence/components/select-data.vue
Normal file
139
src/components/SelectFence/components/select-data.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="userId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="done(record)">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { User, UserParam } from '@/api/system/user/model';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { pageHjmFence } from '@/api/hjm/hjmFence';
|
||||
import { HjmFenceParam } from '@/api/hjm/hjmFence/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
title?: string;
|
||||
organizationId?: number;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: User): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
// 表单数据
|
||||
const { where } = useSearch<HjmFenceParam>({
|
||||
type: undefined,
|
||||
keywords: ''
|
||||
});
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
// {
|
||||
// key: 'index',
|
||||
// width: 48,
|
||||
// align: 'center',
|
||||
// fixed: 'left',
|
||||
// hideInSetting: true,
|
||||
// customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
// },
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id'
|
||||
},
|
||||
{
|
||||
title: '围栏名称',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
// {
|
||||
// title: '经纬度',
|
||||
// dataIndex: 'location',
|
||||
// key: 'location'
|
||||
// },
|
||||
// {
|
||||
// title: '半径',
|
||||
// dataIndex: 'district'
|
||||
// },
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
return pageHjmFence({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: UserParam) => {
|
||||
// selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const done = (record: User) => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
74
src/components/SelectFence/index.vue
Normal file
74
src/components/SelectFence/index.vue
Normal file
@@ -0,0 +1,74 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon>
|
||||
<BulbOutlined class="ele-text-warning" />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<select-data
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
:organizationId="organizationId"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref, watch } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { HjmFence } from '@/api/hjm/hjmFence/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
organizationId?: number;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', HjmFence): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<HjmFence | null>(null);
|
||||
const content = ref<HjmFence>(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: HjmFence) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = (row?: HjmFence) => {
|
||||
emit('done', row);
|
||||
};
|
||||
// 查询租户列表
|
||||
// const appList = ref<App[] | undefined>([]);
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(visible) => {
|
||||
content.value = visible;
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
179
src/components/SelectFile/components/file-record-edit.vue
Normal file
179
src/components/SelectFile/components/file-record-edit.vue
Normal file
@@ -0,0 +1,179 @@
|
||||
<!-- 角色编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="600"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '编辑' : '上传文件'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
okText="保存"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="form"
|
||||
:rules="rules"
|
||||
:label-col="styleResponsive ? { md: 5, sm: 5, xs: 24 } : { flex: '90px' }"
|
||||
:wrapper-col="
|
||||
styleResponsive ? { md: 19, sm: 19, xs: 24 } : { flex: '1' }
|
||||
"
|
||||
>
|
||||
<a-form-item label="文件名称" name="name">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入文件名称"
|
||||
v-model:value="form.name"
|
||||
@pressEnter="save"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="设置分组" name="name">
|
||||
<SelectDict
|
||||
dict-code="groupId"
|
||||
:placeholder="`选择分组`"
|
||||
v-model:value="form.groupName"
|
||||
@done="chooseGroupId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="描述" name="comments">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="图片描述"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type { FormInstance, Rule } from 'ant-design-vue/es/form';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import useFormData from '@/utils/use-form-data';
|
||||
import type { FileRecord } from '@/api/system/file/model';
|
||||
import { addFiles, updateFiles } from '@/api/system/file';
|
||||
import { RuleObject } from 'ant-design-vue/es/form';
|
||||
import { DictData } from '@/api/system/dict-data/model';
|
||||
import { listDictData } from '@/api/system/dict-data';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: FileRecord | null;
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
const fileName = ref('');
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<FileRecord>({
|
||||
id: 0,
|
||||
name: '',
|
||||
comments: '',
|
||||
groupId: undefined,
|
||||
groupName: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
fileName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请上传文件',
|
||||
type: 'string',
|
||||
trigger: 'blur',
|
||||
validator: async (_rule: RuleObject) => {
|
||||
if (!isUpdate.value && fileName.value.length == 0) {
|
||||
return Promise.reject('请上传文件');
|
||||
}
|
||||
return Promise.resolve();
|
||||
}
|
||||
}
|
||||
],
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入文件名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const chooseGroupId = (item: DictData) => {
|
||||
form.groupId = item.dictDataId;
|
||||
form.groupName = item.dictDataName;
|
||||
};
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const saveOrUpdate = isUpdate.value ? updateFiles : addFiles;
|
||||
saveOrUpdate(form)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
assignFields(props.data);
|
||||
listDictData({ dictDataId: props.data.groupId }).then((data) => {
|
||||
if (data.length > 0) {
|
||||
form.groupName = data[0].dictDataName;
|
||||
}
|
||||
});
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
372
src/components/SelectFile/components/select-data.vue
Normal file
372
src/components/SelectFile/components/select-data.vue
Normal file
@@ -0,0 +1,372 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
width="75%"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<div class="ele-cell">
|
||||
<div class="ele-cell-content">
|
||||
<a-space>
|
||||
<a-upload
|
||||
v-if="type == 'video'"
|
||||
:show-upload-list="false"
|
||||
:accept="'video/*'"
|
||||
:customRequest="onUpload"
|
||||
>
|
||||
<a-button type="primary" class="ele-btn-icon">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
<span>上传视频</span>
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<a-upload
|
||||
v-else
|
||||
:show-upload-list="false"
|
||||
:accept="'image/*,application/*'"
|
||||
:customRequest="onUpload"
|
||||
>
|
||||
<a-button type="primary" class="ele-btn-icon">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
<span>上传图片</span>
|
||||
</a-button>
|
||||
</a-upload>
|
||||
<a-select
|
||||
show-search
|
||||
allow-clear
|
||||
v-model:value="dictDataId"
|
||||
optionFilterProp="label"
|
||||
:options="groupList"
|
||||
style="margin-left: 20px; width: 200px"
|
||||
placeholder="请选择分组"
|
||||
@select="onGroupId"
|
||||
/>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 240px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
<a-button type="text" @click="openUrl('/cms/photo/dict')"
|
||||
>管理分组</a-button
|
||||
>
|
||||
<a-alert message="双击行也可以选择" banner closable />
|
||||
</a-space>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'path'">
|
||||
<!-- 文件类型 -->
|
||||
<template v-if="!isImage(record.path)">
|
||||
<span class="ele-text-secondary">[文件]</span>
|
||||
</template>
|
||||
<!-- 含http -->
|
||||
<template v-else-if="record.path.indexOf('http') == 0">
|
||||
<a-image
|
||||
:src="`${record.path}`"
|
||||
:preview="{
|
||||
src: `${record.downloadUrl}`
|
||||
}"
|
||||
:width="100"
|
||||
/>
|
||||
</template>
|
||||
<!-- path -->
|
||||
<template v-else>
|
||||
<a-image
|
||||
:src="`https://oss.wsdns.cn/${record.path}`"
|
||||
:width="120"
|
||||
/>
|
||||
</template>
|
||||
</template>
|
||||
<template v-if="column.dataIndex === 'name'">
|
||||
<a-space class="ele-cell" style="display: flex">
|
||||
<span>{{ record.name }}</span>
|
||||
<EditOutlined title="编辑" @click="openEdit(record)" />
|
||||
</a-space>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<template v-if="pageId == record.pageId">
|
||||
<a-radio v-model:checked="checked" @click="onRadio(record)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-space>
|
||||
<!-- <a-radio @click="onRadio(record)" />-->
|
||||
<a class="ele-text-success" @click="onRadio(record)">选择</a>
|
||||
<a-divider type="vertical" />
|
||||
<a-popconfirm
|
||||
title="确定要删除此记录吗?"
|
||||
@confirm="remove(record)"
|
||||
>
|
||||
<a class="ele-text-danger">删除</a>
|
||||
</a-popconfirm>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
<!-- 编辑弹窗 -->
|
||||
<FileRecordEdit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import {
|
||||
pageFiles,
|
||||
removeFile,
|
||||
uploadOss,
|
||||
uploadOssByGroupId
|
||||
} from '@/api/system/file';
|
||||
import { EleProTable, messageLoading } from 'ele-admin-pro';
|
||||
import { FileRecord, FileRecordParam } from '@/api/system/file/model';
|
||||
import { EditOutlined, UploadOutlined } from '@ant-design/icons-vue';
|
||||
import { DictData } from '@/api/system/dict-data/model';
|
||||
import { pageDictData } from '@/api/system/dict-data';
|
||||
import { isImage, isMobileDevice, openUrl } from '@/utils/common';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import FileRecordEdit from './file-record-edit.vue';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 文件类型
|
||||
type?: string;
|
||||
// 修改回显的数据
|
||||
data?: FileRecord | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: FileRecord): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
const pageId = ref<number>(0);
|
||||
const checked = ref<boolean>(true);
|
||||
const groupList = ref<DictData[]>();
|
||||
const showEdit = ref<boolean>(false);
|
||||
const current = ref<FileRecord | null>();
|
||||
const dictDataId = ref<any>(undefined);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'id'
|
||||
},
|
||||
{
|
||||
title: '文件',
|
||||
dataIndex: 'path',
|
||||
key: 'path'
|
||||
},
|
||||
{
|
||||
title: '名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '大小',
|
||||
dataIndex: 'length',
|
||||
key: 'length',
|
||||
customRender: ({ text }) => {
|
||||
if (text < 1024) {
|
||||
return text + 'B';
|
||||
} else if (text < 1024 * 1024) {
|
||||
return (text / 1024).toFixed(1) + 'KB';
|
||||
} else if (text < 1024 * 1024 * 1024) {
|
||||
return (text / 1024 / 1024).toFixed(1) + 'M';
|
||||
} else {
|
||||
return (text / 1024 / 1024 / 1024).toFixed(1) + 'G';
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.name = searchText.value;
|
||||
}
|
||||
if (dictDataId.value) {
|
||||
where.groupId = dictDataId.value;
|
||||
}
|
||||
if (props.type == 'video') {
|
||||
where.contentType = props.type;
|
||||
}
|
||||
if (props.type == 'image') {
|
||||
where.contentType = props.type;
|
||||
}
|
||||
return pageFiles({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: FileRecordParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const onRadio = (record: FileRecord) => {
|
||||
pageId.value = Number(record.id);
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
|
||||
const getGroupList = () => {
|
||||
pageDictData({ dictCode: 'groupId' }).then((res) => {
|
||||
groupList.value = res?.list.map((d) => {
|
||||
return {
|
||||
label: d.dictDataName,
|
||||
value: d.dictDataId
|
||||
};
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
const onGroupId = (index: number) => {
|
||||
dictDataId.value = index;
|
||||
reload();
|
||||
};
|
||||
|
||||
// 上传文件
|
||||
const onUpload = (item) => {
|
||||
const { file } = item;
|
||||
if (!file.type.startsWith('image') && props.type != 'video') {
|
||||
message.error('只能选择图片');
|
||||
return;
|
||||
}
|
||||
if (props.type == 'video') {
|
||||
if (file.size / 1024 / 1024 > 100) {
|
||||
message.error('大小不能超过 100MB');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
if (file.size / 1024 / 1024 > 10) {
|
||||
message.error('大小不能超过 10MB');
|
||||
return;
|
||||
}
|
||||
}
|
||||
const hide = messageLoading({
|
||||
content: '上传中..',
|
||||
duration: 0,
|
||||
mask: true
|
||||
});
|
||||
if (dictDataId.value > 0) {
|
||||
uploadOssByGroupId(file, dictDataId.value)
|
||||
.then(() => {
|
||||
hide();
|
||||
message.success('上传成功');
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
hide();
|
||||
});
|
||||
} else {
|
||||
uploadOss(file)
|
||||
.then(() => {
|
||||
hide();
|
||||
message.success('上传成功');
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
hide();
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const openEdit = (row?: FileRecord) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: FileRecord) => {
|
||||
const hide = message.loading('请求中..', 0);
|
||||
removeFile(row.id)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: FileRecord) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
// onClick: () => {
|
||||
// updateVisible(false);
|
||||
// emit('done', record);
|
||||
// },
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
updateVisible(false);
|
||||
if (!isMobileDevice()) {
|
||||
record.path = record.url || record.downloadUrl;
|
||||
}
|
||||
console.log(record, 'rec......');
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
getGroupList();
|
||||
reload();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
139
src/components/SelectFile/index.vue
Normal file
139
src/components/SelectFile/index.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<a-image-preview-group>
|
||||
<a-space>
|
||||
<template v-for="(item, index) in data" :key="index">
|
||||
<div class="image-upload-item" v-if="isImage(item.url)">
|
||||
<a-image
|
||||
:style="{
|
||||
border: '1px dashed var(--grey-7)',
|
||||
width: width + 'px',
|
||||
height: height + 'px'
|
||||
}"
|
||||
:src="item.url"
|
||||
/>
|
||||
<a class="image-upload-close" @click="onDeleteItem(index)">
|
||||
<CloseOutlined />
|
||||
</a>
|
||||
</div>
|
||||
<div v-else class="image-upload-item">
|
||||
<YoutubeOutlined />
|
||||
<a class="image-upload-close" @click="onDeleteItem(index)">
|
||||
<CloseOutlined />
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
||||
<a-button
|
||||
@click="openEdit"
|
||||
v-if="data?.length < limit"
|
||||
:style="{ width: width + 'px', height: height + 'px' }"
|
||||
class="select-picture-btn ele-text-placeholder"
|
||||
>
|
||||
<PlusOutlined />
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-image-preview-group>
|
||||
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
:type="type"
|
||||
@done="onChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
PlusOutlined,
|
||||
CloseOutlined,
|
||||
YoutubeOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { FileRecord } from '@/api/system/file/model';
|
||||
import { isImage } from '@/utils/common';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
data?: any[];
|
||||
width?: number;
|
||||
height?: number;
|
||||
type?: string;
|
||||
limit?: number;
|
||||
placeholder?: string;
|
||||
index?: number;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择数据',
|
||||
width: 80,
|
||||
height: 80,
|
||||
limit: 1
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: FileRecord): void;
|
||||
(e: 'del', index: number): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<FileRecord | null>(null);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: FileRecord) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = (row) => {
|
||||
row.index = props.index;
|
||||
emit('done', row);
|
||||
};
|
||||
|
||||
const onDeleteItem = (index: number) => {
|
||||
emit('del', index);
|
||||
};
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.select-picture-btn {
|
||||
background-color: var(--grey-9);
|
||||
border: 1px dashed var(--border-color-base);
|
||||
font-size: 16px;
|
||||
}
|
||||
//.ant-image-img {
|
||||
// width: 100px !important;
|
||||
// height: 100px !important;
|
||||
//}
|
||||
.image-upload-item {
|
||||
position: relative;
|
||||
}
|
||||
.image-upload-close {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
color: rgb(255, 255, 255);
|
||||
font-size: 10px;
|
||||
border-bottom-left-radius: 18px;
|
||||
border-top-right-radius: 2px;
|
||||
background: rgba(0, 0, 0, 0.6);
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 1px;
|
||||
line-height: 1;
|
||||
box-sizing: border-box;
|
||||
padding: 2px 0 0 5px;
|
||||
transition: background-color 0.2s ease-in-out 0s;
|
||||
cursor: pointer;
|
||||
z-index: 2;
|
||||
//display: flex;
|
||||
//justify-content: center;
|
||||
//align-items: center;
|
||||
}
|
||||
.image-upload-close:hover {
|
||||
background-color: var(--red-6);
|
||||
}
|
||||
</style>
|
||||
143
src/components/SelectForm/components/select-data.vue
Normal file
143
src/components/SelectForm/components/select-data.vue
Normal file
@@ -0,0 +1,143 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="formId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<template v-if="pageId == record.formId">
|
||||
<a-radio v-model:checked="checked" @click="onRadio(record)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<a-radio @click="onRadio(record)" />
|
||||
<span class="ele-text-secondary">选择</span>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import { Form } from '@/api/cms/form/model';
|
||||
import { pageForm } from '@/api/cms/form';
|
||||
import { FormParam } from '@/api/cms/form/model';
|
||||
|
||||
defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: Form | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: Form): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
const pageId = ref<number>(0);
|
||||
const checked = ref<boolean>(true);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '操作',
|
||||
width: 180,
|
||||
key: 'action'
|
||||
},
|
||||
{
|
||||
title: '表单ID',
|
||||
dataIndex: 'formId',
|
||||
key: 'formId'
|
||||
},
|
||||
{
|
||||
title: '表单名称',
|
||||
dataIndex: 'name',
|
||||
key: 'name'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.title = searchText.value;
|
||||
}
|
||||
return pageForm({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: FormParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const onRadio = (record: Form) => {
|
||||
pageId.value = Number(record.formId);
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Form) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
// onClick: () => {
|
||||
// updateVisible(false);
|
||||
// emit('done', record);
|
||||
// },
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
60
src/components/SelectForm/index.vue
Normal file
60
src/components/SelectForm/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { Form } from '@/api/cms/form/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择数据'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', Form): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<Form | null>(null);
|
||||
const content = ref(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Form) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('done', content.value);
|
||||
};
|
||||
</script>
|
||||
138
src/components/SelectGoodsCategory/index.vue
Normal file
138
src/components/SelectGoodsCategory/index.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<!-- 省市区级联选择器 -->
|
||||
<template>
|
||||
<a-cascader
|
||||
:value="value"
|
||||
:options="regionsData"
|
||||
:show-search="showSearch"
|
||||
:placeholder="placeholder"
|
||||
dropdown-class-name="ele-pop-wrap-higher"
|
||||
@update:value="updateValue"
|
||||
@change="onChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, reactive } from 'vue';
|
||||
import type { ValueType } from 'ant-design-vue/es/vc-cascader/Cascader';
|
||||
import { listShopGoodsCategory } from '@/api/shop/shopGoodsCategory';
|
||||
import { toTreeData } from 'ele-admin-pro/es';
|
||||
import { ShopGoodsCategory } from '@/api/shop/shopGoodsCategory/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: string[];
|
||||
placeholder?: string;
|
||||
options?: ShopGoodsCategory[];
|
||||
valueField?: 'label';
|
||||
type?: 'provinceCity' | 'province';
|
||||
showSearch?: boolean;
|
||||
merchantId?: number;
|
||||
}>(),
|
||||
{
|
||||
showSearch: true
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', item?: any, value?: ValueType);
|
||||
(e: 'update:value', value?: string[]): void;
|
||||
(e: 'load-data-done', value: ShopGoodsCategory[]): void;
|
||||
}>();
|
||||
|
||||
// 搜索表单
|
||||
const where = reactive<ShopGoodsCategory>({
|
||||
merchantId: undefined
|
||||
});
|
||||
|
||||
// 级联选择器数据
|
||||
const regionsData = ref<ShopGoodsCategory[]>([]);
|
||||
|
||||
/* 更新 value */
|
||||
const updateValue = (value: ValueType) => {
|
||||
emit('update:value', value as string[]);
|
||||
};
|
||||
|
||||
const onChange = (item: any, value: ValueType) => {
|
||||
console.log(item, value);
|
||||
emit('done', item, value);
|
||||
};
|
||||
|
||||
/* 级联选择器数据 value 处理 */
|
||||
const formatData = (data: ShopGoodsCategory[]) => {
|
||||
if (props.valueField === 'label') {
|
||||
return data.map((d) => {
|
||||
const item: ShopGoodsCategory = {
|
||||
label: d.title,
|
||||
value: d.categoryId
|
||||
};
|
||||
if (d.children) {
|
||||
item.children = d.children.map((c) => {
|
||||
const cItem: ShopGoodsCategory = {
|
||||
label: c.title,
|
||||
value: c.categoryId
|
||||
};
|
||||
if (c.children) {
|
||||
cItem.children = c.children.map((cc) => {
|
||||
return {
|
||||
label: cc.title,
|
||||
value: cc.categoryId
|
||||
};
|
||||
});
|
||||
}
|
||||
return cItem;
|
||||
});
|
||||
}
|
||||
return item;
|
||||
});
|
||||
} else {
|
||||
return data;
|
||||
}
|
||||
};
|
||||
|
||||
/* 省市区数据筛选 */
|
||||
const filterData = (data: ShopGoodsCategory[]) => {
|
||||
return formatData(
|
||||
data.map((d) => {
|
||||
const item: ShopGoodsCategory = {
|
||||
label: d.title,
|
||||
value: d.categoryId
|
||||
};
|
||||
if (d.children) {
|
||||
item.children = d.children.map((c) => {
|
||||
return {
|
||||
label: c.title,
|
||||
value: c.categoryId
|
||||
};
|
||||
});
|
||||
}
|
||||
return item;
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.options,
|
||||
() => {
|
||||
console.log(props.merchantId);
|
||||
if (props.merchantId) {
|
||||
where.merchantId = props.merchantId;
|
||||
}
|
||||
listShopGoodsCategory(where).then((data) => {
|
||||
const list = toTreeData({
|
||||
data: data?.map((d) => {
|
||||
d.value = d.categoryId;
|
||||
d.label = d.title;
|
||||
return d;
|
||||
}),
|
||||
idField: 'categoryId',
|
||||
parentIdField: 'parentId'
|
||||
});
|
||||
regionsData.value = filterData(list ?? []);
|
||||
emit('load-data-done', data);
|
||||
});
|
||||
},
|
||||
{
|
||||
immediate: true
|
||||
}
|
||||
);
|
||||
</script>
|
||||
25
src/components/SelectGoodsCategory/load-data.ts
Normal file
25
src/components/SelectGoodsCategory/load-data.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import request from '@/utils/request';
|
||||
import type { IndustryData } from './types';
|
||||
const BASE_URL = import.meta.env.BASE_URL;
|
||||
let reqPromise: Promise<IndustryData[]>;
|
||||
|
||||
/**
|
||||
* 获取省市区数据
|
||||
*/
|
||||
export function getIndustryData() {
|
||||
if (!reqPromise) {
|
||||
reqPromise = new Promise<IndustryData[]>((resolve, reject) => {
|
||||
request
|
||||
.get<IndustryData[]>(BASE_URL + 'json/industry-data.json', {
|
||||
baseURL: ''
|
||||
})
|
||||
.then((res) => {
|
||||
resolve(res.data ?? []);
|
||||
})
|
||||
.catch((e) => {
|
||||
reject(e);
|
||||
});
|
||||
});
|
||||
}
|
||||
return reqPromise;
|
||||
}
|
||||
15
src/components/SelectGoodsCategory/types/index.ts
Normal file
15
src/components/SelectGoodsCategory/types/index.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 行业类型
|
||||
*/
|
||||
export interface IndustryData {
|
||||
label: string;
|
||||
value: string;
|
||||
children?: {
|
||||
value: string;
|
||||
label: string;
|
||||
children?: {
|
||||
value: string;
|
||||
label: string;
|
||||
}[];
|
||||
}[];
|
||||
}
|
||||
137
src/components/SelectGrade/components/select-data.vue
Normal file
137
src/components/SelectGrade/components/select-data.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="gradeId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'companyLogo'">
|
||||
<a-image
|
||||
v-if="record.companyLogo"
|
||||
:src="FILE_THUMBNAIL + record.companyLogo"
|
||||
:preview="false"
|
||||
:width="45"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { pageGrade } from '@/api/user/grade';
|
||||
import { FILE_THUMBNAIL } from '@/config/setting';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import { Grade, GradeParam } from '@/api/user/grade/model';
|
||||
|
||||
defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: Grade | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: Grade): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '等级名称',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
where.isStaff = true;
|
||||
return pageGrade({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: GradeParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Grade) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
60
src/components/SelectGrade/index.vue
Normal file
60
src/components/SelectGrade/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { Grade } from '@/api/user/grade/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择会员等级'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', Customer): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<Grade | null>(null);
|
||||
const content = ref<any>(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Grade) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('done', content.value);
|
||||
};
|
||||
</script>
|
||||
137
src/components/SelectGroup/components/select-data.vue
Normal file
137
src/components/SelectGroup/components/select-data.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="groupId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'companyLogo'">
|
||||
<a-image
|
||||
v-if="record.companyLogo"
|
||||
:src="FILE_THUMBNAIL + record.companyLogo"
|
||||
:preview="false"
|
||||
:width="45"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { pageGroup } from '@/api/system/user-group';
|
||||
import { FILE_THUMBNAIL } from '@/config/setting';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import { Group, GroupParam } from '@/api/system/user-group/model';
|
||||
|
||||
defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: Group | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: Group): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '分组名称',
|
||||
dataIndex: 'name'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
where.isStaff = true;
|
||||
return pageGroup({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: GroupParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Group) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
60
src/components/SelectGroup/index.vue
Normal file
60
src/components/SelectGroup/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { Group } from '@/api/system/user-group/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '选择会员分组'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', Group): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<Group | null>(null);
|
||||
const content = ref<any>(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Group) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('done', content.value);
|
||||
};
|
||||
</script>
|
||||
164
src/components/SelectMerchant/components/select-data.vue
Normal file
164
src/components/SelectMerchant/components/select-data.vue
Normal file
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="merchantId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image
|
||||
v-if="record.image"
|
||||
:src="record.image"
|
||||
:preview="false"
|
||||
:width="45"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'shopType'">
|
||||
<a-tag v-if="record.shopType === 10">企业</a-tag>
|
||||
<a-tag v-if="record.shopType === 20">政府单位</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-button type="primary" @click="onRadio(record)">选择</a-button>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { pageShopMerchant } from '@/api/shop/shopMerchant';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import {
|
||||
ShopMerchant,
|
||||
ShopMerchantParam
|
||||
} from '@/api/shop/shopMerchant/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 商户类型
|
||||
shopType?: string;
|
||||
type?: string;
|
||||
// 修改回显的数据
|
||||
data?: ShopMerchant | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: ShopMerchant): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'LOGO',
|
||||
dataIndex: 'image',
|
||||
key: 'image',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '商户名称',
|
||||
dataIndex: 'merchantName'
|
||||
},
|
||||
{
|
||||
title: '商户类型',
|
||||
dataIndex: 'shopType'
|
||||
// key: 'shopType'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
if (props.shopType == 'empty') {
|
||||
where.emptyType = true;
|
||||
} else {
|
||||
where.shopType = props.shopType;
|
||||
}
|
||||
if (props.type) {
|
||||
where.type = props.type;
|
||||
}
|
||||
return pageShopMerchant({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopMerchantParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const onRadio = (record: ShopMerchant) => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopMerchant) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
// onClick: () => {
|
||||
// updateVisible(false);
|
||||
// emit('done', record);
|
||||
// },
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
64
src/components/SelectMerchant/index.vue
Normal file
64
src/components/SelectMerchant/index.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
:customer-type="customerType"
|
||||
:shopType="shopType"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { ShopMerchant } from '@/api/shop/shopMerchant/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
customerType?: string;
|
||||
placeholder?: string;
|
||||
shopType?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择商户'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', ShopMerchant): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
const content = ref<string>(props.value);
|
||||
// 当前编辑数据
|
||||
const current = ref<ShopMerchant | null>(null);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: ShopMerchant) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = (row) => {
|
||||
emit('done', row);
|
||||
};
|
||||
</script>
|
||||
147
src/components/SelectModel/components/select-data.vue
Normal file
147
src/components/SelectModel/components/select-data.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="500"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<div class="ele-text-heading cursor-pointer" @click="onRadio(record)">
|
||||
{{ record.name }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'model'">
|
||||
<div class="ele-text-heading cursor-pointer" @click="onRadio(record)">
|
||||
{{ record.model }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'comments'">
|
||||
<a-popover>
|
||||
<template #content>
|
||||
{{ record.comments }}
|
||||
</template>
|
||||
<ExclamationCircleOutlined />
|
||||
</a-popover>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a class="block cursor-pointer" @click="onRadio(record)">选择</a>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import { pageCmsModel } from '@/api/cms/cmsModel';
|
||||
import { CmsModel, CmsModelParam } from '@/api/cms/cmsModel/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: CmsModel | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: CmsModel): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '模型',
|
||||
dataIndex: 'name',
|
||||
width: 180,
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '标识',
|
||||
width: 120,
|
||||
dataIndex: 'model',
|
||||
key: 'model'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
return pageCmsModel({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: CmsModelParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const onRadio = (record: CmsModel) => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: CmsModel) => {
|
||||
return {
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
62
src/components/SelectModel/index.vue
Normal file
62
src/components/SelectModel/index.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
:customer-type="customerType"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { Company } from '@/api/system/company/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
customerType?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择数据'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', Customer): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
const content = ref<any>(props.value);
|
||||
// 当前编辑数据
|
||||
const current = ref<Company | null>(null);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Company) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('done', content.value);
|
||||
};
|
||||
</script>
|
||||
139
src/components/SelectModules/components/select-data.vue
Normal file
139
src/components/SelectModules/components/select-data.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space :size="10">
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
<a-button @click="openUrl('/system/modules')">模块管理</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { pageModules } from '@/api/system/modules';
|
||||
import { Modules, ModulesParam } from '@/api/system/modules/model';
|
||||
import { openUrl } from '@/utils/common';
|
||||
|
||||
defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: Modules | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: Modules): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
// 表单数据
|
||||
const { where } = useSearch<ModulesParam>({
|
||||
id: 0,
|
||||
modules: '',
|
||||
modulesUrl: ''
|
||||
});
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '模块ID',
|
||||
dataIndex: 'modules'
|
||||
},
|
||||
{
|
||||
title: 'URL',
|
||||
dataIndex: 'modulesUrl'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ where }) => {
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
return pageModules({
|
||||
...where
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ModulesParam) => {
|
||||
// selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Modules) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
62
src/components/SelectModules/index.vue
Normal file
62
src/components/SelectModules/index.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<select-data
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { Environment } from '@/api/system/environment/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择数据源'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', Environment): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<Environment | null>(null);
|
||||
const content = ref<any>(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Environment) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('done', content.value);
|
||||
};
|
||||
// 查询租户列表
|
||||
// const appList = ref<App[] | undefined>([]);
|
||||
</script>
|
||||
144
src/components/SelectMpPages/components/select-data.vue
Normal file
144
src/components/SelectMpPages/components/select-data.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<template v-if="pageId == record.id">
|
||||
<a-radio v-model:checked="checked" @click="onRadio(record)" />
|
||||
</template>
|
||||
<template v-else>
|
||||
<lebal>
|
||||
<a-radio @click="onRadio(record)" />
|
||||
<span class="ele-text-secondary">选择</span>
|
||||
</lebal>
|
||||
</template>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { pageCmsMpPages } from '@/api/cms/cmsMpPages';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import type { CmsMpPages, CmsMpPagesParam } from '@/api/cms/cmsMpPages/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: CmsMpPages | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: CmsMpPages): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
const pageId = ref<number>(0);
|
||||
const checked = ref<boolean>(true);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '页面名称',
|
||||
dataIndex: 'title',
|
||||
key: 'title'
|
||||
},
|
||||
{
|
||||
title: '路径',
|
||||
dataIndex: 'path',
|
||||
key: 'path'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
return pageCmsMpPages({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: CmsMpPagesParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const onRadio = (record: CmsMpPages) => {
|
||||
pageId.value = Number(record.id);
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: CmsMpPages) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
// onClick: () => {
|
||||
// updateVisible(false);
|
||||
// emit('done', record);
|
||||
// },
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
60
src/components/SelectMpPages/index.vue
Normal file
60
src/components/SelectMpPages/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="page"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { CmsMpPages } from '@/api/cms/cmsMpPages/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择数据'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', MpPages): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<CmsMpPages | null>(null);
|
||||
const page = ref<any>();
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: CmsMpPages) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('done', page.value);
|
||||
};
|
||||
</script>
|
||||
229
src/components/SelectNavigsation/components/select-data.vue
Normal file
229
src/components/SelectNavigsation/components/select-data.vue
Normal file
@@ -0,0 +1,229 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="navigationId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:parse-data="parseData"
|
||||
:need-page="false"
|
||||
:customRow="customRow"
|
||||
:expand-icon-column-index="1"
|
||||
:expanded-row-keys="expandedRowKeys"
|
||||
cache-key="proNavigationTable"
|
||||
@done="onDone"
|
||||
@expand="onExpand"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-button type="dashed" class="ele-btn-icon" @click="expandAll">
|
||||
展开
|
||||
</a-button>
|
||||
<a-button type="dashed" class="ele-btn-icon" @click="foldAll">
|
||||
折叠
|
||||
</a-button>
|
||||
<a-divider type="vertical" />
|
||||
<a-radio-group v-model:value="position" @change="reload">
|
||||
<a-radio-button :value="1">顶部</a-radio-button>
|
||||
<a-radio-button :value="2">底部</a-radio-button>
|
||||
</a-radio-group>
|
||||
<a-divider type="vertical" />
|
||||
<!-- 搜索表单 -->
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'title'">
|
||||
<div class="cursor-pointer" @click="onRadio(record)">
|
||||
{{ record.title }}
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'path'">
|
||||
<div class="text-gray-400">{{ record.path }}</div>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
listCmsNavigation,
|
||||
pageCmsNavigation
|
||||
} from '@/api/cms/cmsNavigation';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { toTreeData, eachTreeData } from 'ele-admin-pro/es';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem,
|
||||
EleProTableDone
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import type {
|
||||
CmsNavigation,
|
||||
CmsNavigationParam
|
||||
} from '@/api/cms/cmsNavigation/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: CmsNavigation | null;
|
||||
// 当前模型
|
||||
model?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: CmsNavigation): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const { locale } = useI18n();
|
||||
const pageId = ref<number>(0);
|
||||
const checked = ref<boolean>(true);
|
||||
// 表格展开的行
|
||||
const expandedRowKeys = ref<number[]>([]);
|
||||
const searchText = ref('');
|
||||
const position = ref(1);
|
||||
// 菜单数据
|
||||
const menuData = ref<CmsNavigation[]>([]);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'navigationId',
|
||||
width: 80
|
||||
},
|
||||
{
|
||||
title: '栏目名称',
|
||||
dataIndex: 'title',
|
||||
key: 'title'
|
||||
},
|
||||
{
|
||||
title: '路由地址',
|
||||
dataIndex: 'path',
|
||||
key: 'path'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ where }) => {
|
||||
where = {};
|
||||
where.title = searchText.value;
|
||||
// where.position = position.value;
|
||||
where.top = 0;
|
||||
where.bottom = undefined;
|
||||
if (position.value == 1) {
|
||||
where.top = 0;
|
||||
where.bottom = undefined;
|
||||
}
|
||||
if (position.value == 2) {
|
||||
where.top = undefined;
|
||||
where.bottom = 0;
|
||||
}
|
||||
where.isMpWeixin = false;
|
||||
// where.lang = locale.value || undefined;
|
||||
return listCmsNavigation({ ...where });
|
||||
};
|
||||
|
||||
/* 表格渲染完成回调 */
|
||||
const onDone: EleProTableDone<CmsNavigation> = ({ data }) => {
|
||||
menuData.value = data;
|
||||
};
|
||||
|
||||
/* 数据转为树形结构 */
|
||||
const parseData = (data: CmsNavigation[]) => {
|
||||
return toTreeData({
|
||||
data: data.map((d) => {
|
||||
return { ...d, key: d.navigationId, value: d.navigationId };
|
||||
}),
|
||||
idField: 'navigationId',
|
||||
parentIdField: 'parentId'
|
||||
});
|
||||
};
|
||||
|
||||
/* 展开全部 */
|
||||
const expandAll = () => {
|
||||
let keys: number[] = [];
|
||||
eachTreeData(menuData.value, (d) => {
|
||||
if (d.children && d.children.length && d.navigationId) {
|
||||
keys.push(d.navigationId);
|
||||
}
|
||||
});
|
||||
expandedRowKeys.value = keys;
|
||||
};
|
||||
|
||||
/* 折叠全部 */
|
||||
const foldAll = () => {
|
||||
expandedRowKeys.value = [];
|
||||
};
|
||||
|
||||
/* 点击展开图标时触发 */
|
||||
const onExpand = (expanded: boolean, record: CmsNavigation) => {
|
||||
if (expanded) {
|
||||
expandedRowKeys.value = [
|
||||
...expandedRowKeys.value,
|
||||
record.navigationId as number
|
||||
];
|
||||
} else {
|
||||
expandedRowKeys.value = expandedRowKeys.value.filter(
|
||||
(d) => d !== record.navigationId
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: CmsNavigationParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const onRadio = (record: CmsNavigation) => {
|
||||
pageId.value = Number(record.navigationId);
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: CmsNavigation) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
// onClick: () => {
|
||||
// updateVisible(false);
|
||||
// emit('done', record);
|
||||
// },
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
62
src/components/SelectNavigsation/index.vue
Normal file
62
src/components/SelectNavigsation/index.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="item"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
:model="model"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import type { CmsNavigation } from '@/api/cms/cmsNavigation/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
model?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: CmsNavigation): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<CmsNavigation | null>(null);
|
||||
const item = ref(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: CmsNavigation) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('done', item.value);
|
||||
};
|
||||
</script>
|
||||
224
src/components/SelectOrganization/components/select-data.vue
Normal file
224
src/components/SelectOrganization/components/select-data.vue
Normal file
@@ -0,0 +1,224 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<!-- 表格 -->
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="organizationId"
|
||||
:columns="columns"
|
||||
:datasource="datasource"
|
||||
:parse-data="parseData"
|
||||
:customRow="customRow"
|
||||
:need-page="false"
|
||||
:expand-icon-column-index="1"
|
||||
:expanded-row-keys="expandedRowKeys"
|
||||
cache-key="proSelectOrgTable"
|
||||
@done="onDone"
|
||||
@expand="onExpand"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-button type="dashed" class="ele-btn-icon" @click="expandAll">
|
||||
展开全部
|
||||
</a-button>
|
||||
<a-button type="dashed" class="ele-btn-icon" @click="foldAll">
|
||||
折叠全部
|
||||
</a-button>
|
||||
<!-- 搜索表单 -->
|
||||
<!-- <a-input-search-->
|
||||
<!-- allow-clear-->
|
||||
<!-- v-model:value="searchText"-->
|
||||
<!-- placeholder="请输入搜索关键词"-->
|
||||
<!-- @search="search"-->
|
||||
<!-- @pressEnter="search"-->
|
||||
<!-- />-->
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'title'">
|
||||
<a-tooltip :title="`分类ID:${record.categoryId}`">
|
||||
<span>{{ record.title }}</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-if="column.key === 'status'">
|
||||
<a-tag v-if="record.status === 0" color="green">显示</a-tag>
|
||||
<a-tag v-if="record.status === 1" color="orange">隐藏</a-tag>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem,
|
||||
EleProTableDone
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { toTreeData, eachTreeData } from 'ele-admin-pro/es';
|
||||
import type { EleProTable } from 'ele-admin-pro/es';
|
||||
import { listOrganizations } from '@/api/system/organization';
|
||||
import {
|
||||
Organization,
|
||||
OrganizationParam
|
||||
} from '@/api/system/organization/model';
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 120,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '部门名称',
|
||||
dataIndex: 'organizationName'
|
||||
}
|
||||
]);
|
||||
|
||||
// 分类数据
|
||||
const categoryData = ref<Organization[]>([]);
|
||||
|
||||
// 表格展开的行
|
||||
const expandedRowKeys = ref<number[]>([]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ where }) => {
|
||||
return listOrganizations({ ...where });
|
||||
};
|
||||
|
||||
/* 数据转为树形结构 */
|
||||
const parseData = (data) => {
|
||||
return toTreeData({
|
||||
data: data.map((d) => {
|
||||
return { ...d, key: d.organizationId, value: d.organizationId };
|
||||
}),
|
||||
idField: 'organizationId',
|
||||
parentIdField: 'parentId'
|
||||
});
|
||||
};
|
||||
|
||||
/* 表格渲染完成回调 */
|
||||
const onDone: EleProTableDone<Organization> = ({ data }) => {
|
||||
categoryData.value = data;
|
||||
};
|
||||
|
||||
/* 刷新表格 */
|
||||
const reload = (where?: OrganizationParam) => {
|
||||
tableRef?.value?.reload({ where });
|
||||
};
|
||||
|
||||
// import { ref } from 'vue';
|
||||
// import {
|
||||
// ColumnItem,
|
||||
// DatasourceFunction
|
||||
// } from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
// import {
|
||||
// listOrganizations,
|
||||
// pageOrganizations
|
||||
// } from '@/api/system/organization';
|
||||
// import { EleProTable, toTreeData } from 'ele-admin-pro';
|
||||
// import {
|
||||
// Organization,
|
||||
// OrganizationParam
|
||||
// } from '@/api/system/organization/model';
|
||||
// import { message } from 'ant-design-vue/es';
|
||||
// import { ArticleCategory } from '@/api/cms/category/model';
|
||||
//
|
||||
// defineProps<{
|
||||
// // 弹窗是否打开
|
||||
// visible: boolean;
|
||||
// // 标题
|
||||
// title?: string;
|
||||
// // 修改回显的数据
|
||||
// data?: Organization | null;
|
||||
// }>();
|
||||
//
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: Organization): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
//
|
||||
// // 表格实例
|
||||
// const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
//
|
||||
// // 表格配置
|
||||
// const columns = ref<ColumnItem[]>([
|
||||
// {
|
||||
// title: '部门名称',
|
||||
// dataIndex: 'organizationName'
|
||||
// },
|
||||
// {
|
||||
// title: '操作',
|
||||
// key: 'action',
|
||||
// align: 'center'
|
||||
// }
|
||||
// ]);
|
||||
|
||||
/* 展开全部 */
|
||||
const expandAll = () => {
|
||||
let keys: number[] = [];
|
||||
eachTreeData(categoryData.value, (d) => {
|
||||
if (d.children && d.children.length && d.organizationId) {
|
||||
keys.push(d.organizationId);
|
||||
}
|
||||
});
|
||||
expandedRowKeys.value = keys;
|
||||
};
|
||||
|
||||
/* 折叠全部 */
|
||||
const foldAll = () => {
|
||||
expandedRowKeys.value = [];
|
||||
};
|
||||
|
||||
/* 点击展开图标时触发 */
|
||||
const onExpand = (expanded: boolean, record: Organization) => {
|
||||
if (expanded) {
|
||||
expandedRowKeys.value = [
|
||||
...expandedRowKeys.value,
|
||||
record.organizationId as number
|
||||
];
|
||||
} else {
|
||||
expandedRowKeys.value = expandedRowKeys.value.filter(
|
||||
(d) => d !== record.organizationId
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Organization) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
@@ -0,0 +1,123 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="organizationId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { pageOrganizations } from '@/api/system/organization';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import {
|
||||
Organization,
|
||||
OrganizationParam
|
||||
} from '@/api/system/organization/model';
|
||||
|
||||
defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: Organization | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: Organization): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '部门名称',
|
||||
dataIndex: 'organizationName'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
where.isStaff = true;
|
||||
return pageOrganizations({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: OrganizationParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Organization) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
60
src/components/SelectOrganization/index.vue
Normal file
60
src/components/SelectOrganization/index.vue
Normal file
@@ -0,0 +1,60 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<select-data
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { Organization } from '@/api/system/organization/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择数据'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', Organization): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<Organization | null>(null);
|
||||
const content = ref<any>(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Organization) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('done', content.value);
|
||||
};
|
||||
</script>
|
||||
151
src/components/SelectRole/components/select-data.vue
Normal file
151
src/components/SelectRole/components/select-data.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="roleId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'companyLogo'">
|
||||
<a-image
|
||||
v-if="record.companyLogo"
|
||||
:src="FILE_THUMBNAIL + record.companyLogo"
|
||||
:preview="false"
|
||||
:width="45"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { pageRoles } from '@/api/system/role';
|
||||
import { FILE_THUMBNAIL } from '@/config/setting';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import { Role, RoleParam } from '@/api/system/role/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 类型
|
||||
type?: string;
|
||||
// 修改回显的数据
|
||||
data?: Role | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: Role): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '角色名称',
|
||||
dataIndex: 'roleName'
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'comments'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
where.isStaff = true;
|
||||
return pageRoles({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
}).then((res) => {
|
||||
if (props.type == 'merchant') {
|
||||
return res?.list.filter(
|
||||
(d) => d.roleCode == 'merchantClerk' || d.roleCode == 'merchant'
|
||||
);
|
||||
} else {
|
||||
return res;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: RoleParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Role) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
62
src/components/SelectRole/index.vue
Normal file
62
src/components/SelectRole/index.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:type="type"
|
||||
:title="placeholder"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { Grade } from '@/api/user/grade/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
type?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择数据'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', Role): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<Grade | null>(null);
|
||||
const content = ref<any>(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Grade) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('done', content.value);
|
||||
};
|
||||
</script>
|
||||
169
src/components/SelectSpec/components/select-data.vue
Normal file
169
src/components/SelectSpec/components/select-data.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="specId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'image'">
|
||||
<a-image
|
||||
v-if="record.image"
|
||||
:src="record.image"
|
||||
:preview="false"
|
||||
:width="45"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="column.key === 'specValue'">
|
||||
<a-space direction="vertical">
|
||||
<template
|
||||
v-for="(item, index) in JSON.parse(record.specValue)"
|
||||
:key="index"
|
||||
>
|
||||
<div class="text-left">
|
||||
<span class="text-gray-400 mr-2">{{ item.value }} :</span>
|
||||
<ele-tag
|
||||
shape="round"
|
||||
size="small"
|
||||
v-for="(sub, subIndex) in item.detail"
|
||||
:key="subIndex"
|
||||
>
|
||||
{{ sub }}
|
||||
</ele-tag>
|
||||
</div>
|
||||
</template>
|
||||
</a-space>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-radio @click="onRadio(record)" />
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { pageShopSpec } from '@/api/shop/shopSpec';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import { ShopSpec, ShopSpecParam } from '@/api/shop/shopSpec/model';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 商户类型
|
||||
shopType?: string;
|
||||
// 修改回显的数据
|
||||
data?: ShopSpec | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: ShopSpec): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '规格',
|
||||
dataIndex: 'specName',
|
||||
key: 'specName',
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '规格值',
|
||||
dataIndex: 'specValue',
|
||||
key: 'specValue'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
if (props.shopType == 'empty') {
|
||||
where.emptyType = true;
|
||||
} else {
|
||||
where.shopType = props.shopType;
|
||||
}
|
||||
return pageShopSpec({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: ShopSpecParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const onRadio = (record: ShopSpec) => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: ShopSpec) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
// onClick: () => {
|
||||
// updateVisible(false);
|
||||
// emit('done', record);
|
||||
// },
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
62
src/components/SelectSpec/index.vue
Normal file
62
src/components/SelectSpec/index.vue
Normal file
@@ -0,0 +1,62 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
:customer-type="customerType"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { Spec } from '@/api/shop/spec/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
customerType?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择商户'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', Spec): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<Spec | null>(null);
|
||||
const content = ref<any>(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Spec) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = () => {
|
||||
emit('done', content.value);
|
||||
};
|
||||
</script>
|
||||
81
src/components/SelectSpecValue/index.vue
Normal file
81
src/components/SelectSpecValue/index.vue
Normal file
@@ -0,0 +1,81 @@
|
||||
<!-- 选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
:allow-clear="allowClear"
|
||||
:show-search="showSearch"
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
:value="value"
|
||||
:placeholder="placeholder"
|
||||
@update:value="updateValue"
|
||||
:style="`width: ${width}px`"
|
||||
@blur="onBlur"
|
||||
@change="onChange"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { listSpecValue } from '@/api/shop/specValue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
showSearch?: string;
|
||||
allowClear?: boolean;
|
||||
width?: number;
|
||||
specId?: number;
|
||||
index?: number;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择服务器厂商'
|
||||
}
|
||||
);
|
||||
|
||||
// specValues
|
||||
const data = ref<any>([]);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
(e: 'done', value: any, index: number): void;
|
||||
}>();
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
|
||||
const onChange = (value: any) => {
|
||||
console.log(value);
|
||||
data.value.map((d) => {
|
||||
if (d.value == value) {
|
||||
emit('done', d, Number(props.index));
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.specId,
|
||||
(specId) => {
|
||||
if (specId) {
|
||||
listSpecValue({ specId }).then((list) => {
|
||||
data.value = list.map((v) => {
|
||||
v.label = v.specValue;
|
||||
v.value = v.specValue;
|
||||
return v;
|
||||
});
|
||||
});
|
||||
} else {
|
||||
data.value = [];
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
144
src/components/SelectStaff/components/select-data.vue
Normal file
144
src/components/SelectStaff/components/select-data.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="userId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="where.keywords"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'roles'">
|
||||
<a-tag v-for="(item, index) in record.roles" :key="index">{{
|
||||
item.roleName
|
||||
}}</a-tag>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="done(record)">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
import { User, UserParam } from '@/api/system/user/model';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
type?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: User): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单数据
|
||||
const { where } = useSearch<UserParam>({
|
||||
userId: undefined,
|
||||
nickname: undefined,
|
||||
isStaff: true,
|
||||
keywords: ''
|
||||
});
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'userId'
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'nickname'
|
||||
},
|
||||
{
|
||||
title: '所属部门',
|
||||
dataIndex: 'organizationName'
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'roles',
|
||||
key: 'roles'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where.isStaff = true;
|
||||
return pageUsers({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = () => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const done = (record: User) => {
|
||||
if (props.type == 'task') {
|
||||
record.roles?.map((d) => {
|
||||
if (d.roleCode == 'promoter') {
|
||||
message.error('只能指派给受理人员');
|
||||
return false;
|
||||
}
|
||||
});
|
||||
}
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
139
src/components/SelectStaff/components/select-user.vue
Normal file
139
src/components/SelectStaff/components/select-user.vue
Normal file
@@ -0,0 +1,139 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="userId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
import { User, UserParam } from '@/api/system/user/model';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
|
||||
defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
title?: string;
|
||||
organizationId?: number;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: User): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'nickname'
|
||||
},
|
||||
{
|
||||
title: '联系电话',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone'
|
||||
},
|
||||
{
|
||||
title: '所属部门',
|
||||
dataIndex: 'organizationName'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
where.isStaff = true;
|
||||
return pageUsers({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: UserParam) => {
|
||||
// selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: User) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
70
src/components/SelectStaff/index.vue
Normal file
70
src/components/SelectStaff/index.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<select-data
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
:type="type"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { User } from '@/api/system/user/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
index?: number;
|
||||
type?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', User): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<User | null>(null);
|
||||
const content = ref<any>();
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: User) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = (row) => {
|
||||
row.index = Number(props.index);
|
||||
emit('done', row);
|
||||
};
|
||||
|
||||
if (props.value) {
|
||||
content.value = props.value;
|
||||
}
|
||||
// 查询租户列表
|
||||
// const appList = ref<App[] | undefined>([]);
|
||||
</script>
|
||||
137
src/components/SelectStudent/components/select-data.vue
Normal file
137
src/components/SelectStudent/components/select-data.vue
Normal file
@@ -0,0 +1,137 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="userId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<span class="text-orange-400 px-3">操作提示:双击选择</span>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'websiteLogo'">
|
||||
<a-avatar :src="record.websiteLogo" />
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-radio @click="onRadio(record)" />
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import { User, UserParam } from '@/api/system/user/model';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: User): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'LOGO',
|
||||
dataIndex: 'websiteLogo',
|
||||
key: 'websiteLogo',
|
||||
width: 90,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '网站名称',
|
||||
dataIndex: 'websiteName',
|
||||
key: 'websiteName'
|
||||
},
|
||||
{
|
||||
title: '域名',
|
||||
dataIndex: 'domain',
|
||||
key: 'domain'
|
||||
}
|
||||
// {
|
||||
// title: '状态',
|
||||
// dataIndex: 'status',
|
||||
// key: 'status',
|
||||
// align: 'center'
|
||||
// }
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
return pageUsers({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: UserParam) => {
|
||||
tableRef?.value?.reload({ page: 1, ...where });
|
||||
};
|
||||
|
||||
const onRadio = (record: User) => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: User) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
// onClick: () => {
|
||||
// updateVisible(false);
|
||||
// emit('done', record);
|
||||
// },
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
59
src/components/SelectStudent/index.vue
Normal file
59
src/components/SelectStudent/index.vue
Normal file
@@ -0,0 +1,59 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon
|
||||
><BulbOutlined
|
||||
v-if="value > 20"
|
||||
class="ele-text-heading" /><BulbOutlined
|
||||
v-else
|
||||
class="ele-text-danger"
|
||||
/></template>
|
||||
</a-button>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
:customer-type="customerType"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { User } from '@/api/system/user/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
customerType?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择数据'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: User): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<User | null>(null);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: User) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = (row: User) => {
|
||||
emit('done', row);
|
||||
};
|
||||
</script>
|
||||
151
src/components/SelectUser/components/select-data.vue
Normal file
151
src/components/SelectUser/components/select-data.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="userId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-space>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="done(record)">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
import { User, UserParam } from '@/api/system/user/model';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import useSearch from '@/utils/use-search';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
title?: string;
|
||||
organizationId?: number;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: User): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
// 表单数据
|
||||
const { where } = useSearch<UserParam>({
|
||||
userId: undefined,
|
||||
nickname: undefined,
|
||||
isStaff: true,
|
||||
keywords: ''
|
||||
});
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
// {
|
||||
// key: 'index',
|
||||
// width: 48,
|
||||
// align: 'center',
|
||||
// fixed: 'left',
|
||||
// hideInSetting: true,
|
||||
// customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
// },
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'userId'
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'realName'
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'mobile',
|
||||
key: 'mobile'
|
||||
},
|
||||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'nickname'
|
||||
},
|
||||
{
|
||||
title: '所属部门',
|
||||
dataIndex: 'organizationName'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
if (props.organizationId) {
|
||||
where.organizationId = props.organizationId;
|
||||
}
|
||||
return pageUsers({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: UserParam) => {
|
||||
// selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const done = (record: User) => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
138
src/components/SelectUser/components/select-user.vue
Normal file
138
src/components/SelectUser/components/select-user.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="userId"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column }">
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="link">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
import { User, UserParam } from '@/api/system/user/model';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
|
||||
defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: User): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
key: 'index',
|
||||
width: 48,
|
||||
align: 'center',
|
||||
fixed: 'left',
|
||||
hideInSetting: true,
|
||||
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'nickname'
|
||||
},
|
||||
{
|
||||
title: '联系电话',
|
||||
dataIndex: 'phone',
|
||||
key: 'phone'
|
||||
},
|
||||
{
|
||||
title: '所属部门',
|
||||
dataIndex: 'organizationName'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
where.isStaff = true;
|
||||
return pageUsers({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: UserParam) => {
|
||||
// selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: User) => {
|
||||
return {
|
||||
// 行点击事件
|
||||
onClick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
64
src/components/SelectUser/index.vue
Normal file
64
src/components/SelectUser/index.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="user"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon><BulbOutlined class="ele-text-warning" /></template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<select-data
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
:organizationId="organizationId"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { User } from '@/api/system/user/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
organizationId?: number;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', User): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<User | null>(null);
|
||||
const user = ref<User>(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: User) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = (row?: User) => {
|
||||
emit('done', row);
|
||||
};
|
||||
// 查询租户列表
|
||||
// const appList = ref<App[] | undefined>([]);
|
||||
</script>
|
||||
146
src/components/SelectUserByButton/components/select-data.vue
Normal file
146
src/components/SelectUserByButton/components/select-data.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="750"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="where.keywords"
|
||||
placeholder="用户ID|手机号码|姓名"
|
||||
class="w-full mb-3"
|
||||
@search="search"
|
||||
@pressEnter="search"
|
||||
/>
|
||||
<a-table
|
||||
ref="tableRef"
|
||||
row-key="userId"
|
||||
:dataSource="list"
|
||||
:columns="columns"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'avatar'">
|
||||
<a-avatar
|
||||
:size="30"
|
||||
:src="`${record.avatar}`"
|
||||
style="margin-right: 4px"
|
||||
>
|
||||
<template #icon>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
</a-avatar>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a-space>
|
||||
<a-button type="primary" @click="done(record)">选择</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
<div class="flex justify-center py-3">
|
||||
<a-pagination
|
||||
v-model:current="where.page"
|
||||
:total="total"
|
||||
show-less-items
|
||||
@change="reload"
|
||||
/>
|
||||
</div>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { UserOutlined } from '@ant-design/icons-vue';
|
||||
import { ColumnItem } from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
import { User, UserParam } from '@/api/system/user/model';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import useSearch from '@/utils/use-search';
|
||||
|
||||
defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: User | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: User): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const list = ref<User[]>([]);
|
||||
const total = ref<number>(0);
|
||||
// 表单数据
|
||||
const { where } = useSearch<UserParam>({
|
||||
userId: undefined,
|
||||
nickname: undefined,
|
||||
keywords: '',
|
||||
page: 1
|
||||
});
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: 'ID',
|
||||
dataIndex: 'userId'
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'mobile',
|
||||
key: 'mobile'
|
||||
},
|
||||
{
|
||||
title: '头像',
|
||||
dataIndex: 'avatar',
|
||||
key: 'avatar'
|
||||
},
|
||||
{
|
||||
title: '姓名',
|
||||
dataIndex: 'realName'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
const done = (record: User) => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
|
||||
const search = () => {
|
||||
where.page = 1;
|
||||
reload();
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = () => {
|
||||
pageUsers(where).then((res) => {
|
||||
if (res?.list) {
|
||||
list.value = res?.list;
|
||||
total.value = res.count;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
reload();
|
||||
</script>
|
||||
53
src/components/SelectUserByButton/index.vue
Normal file
53
src/components/SelectUserByButton/index.vue
Normal file
@@ -0,0 +1,53 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-button type="primary" :icon="h(UserAddOutlined)" @click="openEdit"
|
||||
>关联会员</a-button
|
||||
>
|
||||
<!-- 选择弹窗 -->
|
||||
<select-data
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined, UserAddOutlined } from '@ant-design/icons-vue';
|
||||
import { ref, h } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { User } from '@/api/system/user/model';
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', User): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<User | null>(null);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: User) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = (row?: User) => {
|
||||
emit('done', row);
|
||||
};
|
||||
// 查询租户列表
|
||||
// const appList = ref<App[] | undefined>([]);
|
||||
</script>
|
||||
169
src/components/SelectWebsiteField/components/select-data.vue
Normal file
169
src/components/SelectWebsiteField/components/select-data.vue
Normal file
@@ -0,0 +1,169 @@
|
||||
<template>
|
||||
<ele-modal
|
||||
width="70%"
|
||||
:visible="visible"
|
||||
:maskClosable="false"
|
||||
:title="title"
|
||||
:footer="null"
|
||||
:body-style="{ paddingBottom: '28px' }"
|
||||
@update:visible="updateVisible"
|
||||
>
|
||||
<ele-pro-table
|
||||
ref="tableRef"
|
||||
row-key="id"
|
||||
:datasource="datasource"
|
||||
:columns="columns"
|
||||
:customRow="customRow"
|
||||
:pagination="false"
|
||||
>
|
||||
<template #toolbar>
|
||||
<a-input-search
|
||||
allow-clear
|
||||
v-model:value="searchText"
|
||||
placeholder="请输入搜索关键词"
|
||||
style="width: 200px"
|
||||
@search="reload"
|
||||
@pressEnter="reload"
|
||||
/>
|
||||
</template>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'name'">
|
||||
<div class="ele-text-heading" @click="onRadio(record)">
|
||||
{{ record.name }}
|
||||
</div>
|
||||
<div class="ele-text-placeholder">{{ record.comments }}</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'value'">
|
||||
<a-image v-if="record.type === 1" :src="record.value" :width="120" />
|
||||
<div v-else>{{ record.value }}</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'defaultValue'">
|
||||
<a-image v-if="record.type === 1" :src="record.value" :width="120" />
|
||||
<div v-else>{{ record.value }}</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'comments'">
|
||||
<a-popover>
|
||||
<template #content>
|
||||
{{ record.comments }}
|
||||
</template>
|
||||
<ExclamationCircleOutlined />
|
||||
</a-popover>
|
||||
</template>
|
||||
<template v-if="column.key === 'action'">
|
||||
<a @click="onRadio(record)">选择</a>
|
||||
</template>
|
||||
</template>
|
||||
</ele-pro-table>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
ColumnItem,
|
||||
DatasourceFunction
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import { EleProTable } from 'ele-admin-pro';
|
||||
import { Company, CompanyParam } from '@/api/system/company/model';
|
||||
import { CmsWebsiteField } from '@/api/cms/cmsWebsiteField/model';
|
||||
import { pageWebsiteField } from '@/api/system/website/field';
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 标题
|
||||
title?: string;
|
||||
// 修改回显的数据
|
||||
data?: CmsWebsiteField | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: Company): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 搜索内容
|
||||
const searchText = ref(null);
|
||||
|
||||
// 表格实例
|
||||
const tableRef = ref<InstanceType<typeof EleProTable> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const columns = ref<ColumnItem[]>([
|
||||
{
|
||||
title: '参数名',
|
||||
dataIndex: 'name',
|
||||
width: 180,
|
||||
key: 'name'
|
||||
},
|
||||
{
|
||||
title: '默认值',
|
||||
width: 120,
|
||||
dataIndex: 'defaultValue',
|
||||
key: 'defaultValue'
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'comments',
|
||||
key: 'comments',
|
||||
width: 120,
|
||||
align: 'center'
|
||||
},
|
||||
{
|
||||
title: '可设置范围',
|
||||
width: 120,
|
||||
align: 'center',
|
||||
dataIndex: 'modifyRange'
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 180,
|
||||
align: 'center',
|
||||
hideInSetting: true
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
where = {};
|
||||
// 搜索条件
|
||||
if (searchText.value) {
|
||||
where.keywords = searchText.value;
|
||||
}
|
||||
return pageWebsiteField({
|
||||
...where,
|
||||
...orders,
|
||||
page,
|
||||
limit
|
||||
});
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: CompanyParam) => {
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
const onRadio = (record: Company) => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
};
|
||||
|
||||
/* 自定义行属性 */
|
||||
const customRow = (record: Company) => {
|
||||
return {
|
||||
// 行双击事件
|
||||
onDblclick: () => {
|
||||
updateVisible(false);
|
||||
emit('done', record);
|
||||
}
|
||||
};
|
||||
};
|
||||
</script>
|
||||
<style lang="less"></style>
|
||||
64
src/components/SelectWebsiteField/index.vue
Normal file
64
src/components/SelectWebsiteField/index.vue
Normal file
@@ -0,0 +1,64 @@
|
||||
<template>
|
||||
<div>
|
||||
<a-input-group compact>
|
||||
<a-input
|
||||
disabled
|
||||
style="width: calc(100% - 32px)"
|
||||
v-model:value="content"
|
||||
:placeholder="placeholder"
|
||||
/>
|
||||
<a-button @click="openEdit">
|
||||
<template #icon>
|
||||
<BulbOutlined class="ele-text-warning" />
|
||||
</template>
|
||||
</a-button>
|
||||
</a-input-group>
|
||||
<!-- 选择弹窗 -->
|
||||
<SelectData
|
||||
v-model:visible="showEdit"
|
||||
:data="current"
|
||||
:title="placeholder"
|
||||
:customer-type="customerType"
|
||||
@done="onChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { BulbOutlined } from '@ant-design/icons-vue';
|
||||
import { ref } from 'vue';
|
||||
import SelectData from './components/select-data.vue';
|
||||
import { CmsWebsiteField } from '@/api/cms/cmsWebsiteField/model';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: any;
|
||||
customerType?: string;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择数据'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done', data: CmsWebsiteField): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
// 当前编辑数据
|
||||
const current = ref<CmsWebsiteField | null>(null);
|
||||
const content = ref<any>(props.value);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: CmsWebsiteField) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
const onChange = (item: CmsWebsiteField) => {
|
||||
emit('done', item);
|
||||
};
|
||||
</script>
|
||||
510
src/components/Simulator/index.vue
Normal file
510
src/components/Simulator/index.vue
Normal file
@@ -0,0 +1,510 @@
|
||||
<template>
|
||||
<div class="phone-layout" v-if="form">
|
||||
<div class="phone-header-black ele-fluid">
|
||||
<div class="title ele-fluid">
|
||||
<div class="title-bar">
|
||||
<span class="back"></span>
|
||||
<span>{{ form.title || '首页' }}</span>
|
||||
<a class="share" @click="onShare"></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- 会员信息卡片 -->
|
||||
<template v-if="form.title == '我的'">
|
||||
<a-popover>
|
||||
<template #content> 点击更换背景 </template>
|
||||
<div
|
||||
class="user-card"
|
||||
:style="{
|
||||
backgroundImage: 'url(' + param.mp_user_top + ')'
|
||||
}"
|
||||
@click="
|
||||
openUserCard({
|
||||
name: 'mp_user_top',
|
||||
value: param.mp_user_top,
|
||||
comments: '小程序我的顶部背景图片',
|
||||
sortNumber: 100
|
||||
})
|
||||
"
|
||||
>
|
||||
<div class="user-avatar">
|
||||
<a-avatar :src="param.site_logo" :size="60" />
|
||||
<div class="user-info">
|
||||
<div class="nickname">昵称</div>
|
||||
<div class="phone">手机号码</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-popover>
|
||||
<UserCardEdit
|
||||
v-model:visible="showUserCardEdit"
|
||||
:data="current"
|
||||
@done="reload"
|
||||
/>
|
||||
</template>
|
||||
<template v-if="form.title">
|
||||
<div class="phone-body" style="overflow-y: auto; overflow-x: hidden">
|
||||
<!-- 幻灯片轮播 -->
|
||||
<template v-if="form.showCarousel">
|
||||
<div
|
||||
class="ad-bar"
|
||||
:style="{
|
||||
backgroundImage: 'url(' + param.mp_user_top + ')'
|
||||
}"
|
||||
@click="onCarousel"
|
||||
>
|
||||
<a-carousel arrows autoplay :dots="true">
|
||||
<template v-if="adImageList">
|
||||
<template v-for="(img, index) in adImageList" :key="index">
|
||||
<div class="ad-item">
|
||||
<a-image
|
||||
:preview="false"
|
||||
:src="img.url"
|
||||
width="100%"
|
||||
height="200px"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
</template>
|
||||
</a-carousel>
|
||||
</div>
|
||||
</template>
|
||||
<!-- 导航菜单 -->
|
||||
<template v-if="mpMenus">
|
||||
<!-- 单排 -->
|
||||
<div class="order-card ele-cell">
|
||||
<div
|
||||
v-for="(item, index) in mpMenus"
|
||||
:key="index"
|
||||
class="ele-cell-content ele-text-center btn-center"
|
||||
@click="openMpMenuEdit(item)"
|
||||
>
|
||||
<a-image :src="item.avatar" :width="40" :preview="false" />
|
||||
<span style="white-space: nowrap">{{ item.title }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<!-- <MpMenuEdit-->
|
||||
<!-- v-model:visible="showMpMenuEdit"-->
|
||||
<!-- :data="current"-->
|
||||
<!-- @done="reload"-->
|
||||
<!-- />-->
|
||||
</template>
|
||||
<!-- 商户列表 -->
|
||||
<template v-if="form.showShopCard">
|
||||
<div class="merchant-card-title">场地预定</div>
|
||||
<div
|
||||
class="merchant-card ele-cell"
|
||||
v-for="(item, index) in shopList"
|
||||
:key="index"
|
||||
>
|
||||
<a-image :src="item.image" :width="96" :preview="false" />
|
||||
<div class="merchant-info ele-cell-content">
|
||||
<div class="merchant-name">{{ item.merchantName }}</div>
|
||||
<div class="merchant-desc ele-cell-desc">
|
||||
{{ item.comments }}
|
||||
</div>
|
||||
</div>
|
||||
<!-- <div class="">-->
|
||||
<!-- <a-button>我要去</a-button>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</template>
|
||||
<!-- 培训课程 -->
|
||||
<template v-if="form.showTtrainCard">
|
||||
<div class="merchant-card-title">培训课程</div>
|
||||
<div
|
||||
class="merchant-card ele-cell"
|
||||
v-for="(item, index) in shopList"
|
||||
:key="index"
|
||||
@click="openMpMenuEdit(item)"
|
||||
>
|
||||
<a-image :src="item.image" :width="96" :preview="false" />
|
||||
<div class="merchant-info ele-cell-content">
|
||||
<div class="merchant-name">{{ item.merchantName }}</div>
|
||||
<div class="merchant-desc ele-cell-desc">
|
||||
{{ item.comments }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</div>
|
||||
</template>
|
||||
<a-card
|
||||
class="buy-bar"
|
||||
:bordered="false"
|
||||
v-if="form"
|
||||
:body-style="{ padding: '12px 16px' }"
|
||||
>
|
||||
<div class="ele-cell">
|
||||
<template v-for="(item, index) in tabBar" :key="index">
|
||||
<a
|
||||
class="home-btn ele-cell-content ele-text-secondary"
|
||||
@click="openUrl(`/mp-design/${item.id}`)"
|
||||
>
|
||||
<component
|
||||
v-if="item.icon"
|
||||
class="icon"
|
||||
:class="form.id === item.id ? 'ele-text-danger' : ''"
|
||||
:is="item.icon"
|
||||
/>
|
||||
<span :class="form.id === item.id ? 'ele-text-danger' : ''">{{
|
||||
item.title
|
||||
}}</span>
|
||||
</a>
|
||||
</template>
|
||||
</div>
|
||||
</a-card>
|
||||
<AdEdit v-model:visible="showAdEdit" :data="ad" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch } from 'vue';
|
||||
import { CmsWebsiteField } from '@/api/cms/cmsWebsiteField/model';
|
||||
import { CmsMpMenu } from '@/api/cms/cmsMpMenu/model';
|
||||
import type { CmsMpPages } from '@/api/cms/cmsMpPages/model';
|
||||
import { openUrl } from '@/utils/common';
|
||||
|
||||
// import MpMenuEdit from './mpMenuEdit.vue';
|
||||
import { CmsAd } from '@/api/cms/cmsAd/model';
|
||||
import AdEdit from '@/views/cms/ad/components/ad-edit.vue';
|
||||
import UserCardEdit from '@/views/cms/field/components/website-field-edit.vue';
|
||||
|
||||
const prpos = withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
form?: CmsMpPages;
|
||||
tabBar?: CmsMpPages[];
|
||||
type?: number;
|
||||
mpMenus?: any[] | null;
|
||||
refresh?: boolean;
|
||||
}>(),
|
||||
{
|
||||
placeholder: undefined
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const param = ref<any>({});
|
||||
const showUserCardEdit = ref(false);
|
||||
const showMpMenuEdit = ref(false);
|
||||
const showAdEdit = ref(false);
|
||||
const ad = ref<CmsAd>();
|
||||
// 当前编辑数据
|
||||
const current = ref<CmsWebsiteField | null>(null);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openUserCard = (row?: CmsWebsiteField) => {
|
||||
current.value = row ?? null;
|
||||
showUserCardEdit.value = true;
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openMpMenuEdit = (row?: CmsMpMenu) => {
|
||||
current.value = row ?? null;
|
||||
showMpMenuEdit.value = true;
|
||||
};
|
||||
|
||||
const onShare = () => {};
|
||||
|
||||
const onCarousel = (row?: CmsAd) => {
|
||||
showAdEdit.value = true;
|
||||
};
|
||||
|
||||
const reload = () => {
|
||||
emit('done');
|
||||
};
|
||||
|
||||
reload();
|
||||
|
||||
watch(
|
||||
() => prpos.refresh,
|
||||
(refresh) => {
|
||||
if (refresh) {
|
||||
reload();
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as MenuIcons from '@/layout/menu-icons';
|
||||
export default {
|
||||
name: 'Simulator',
|
||||
components: MenuIcons
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.phone-layout {
|
||||
position: fixed;
|
||||
right: 26px;
|
||||
width: 390px;
|
||||
height: 844px;
|
||||
background: url('@/assets/img/app-ui.png');
|
||||
background-repeat: no-repeat;
|
||||
background-position: top;
|
||||
background-size: 100%;
|
||||
//position: relative;
|
||||
padding: 0 16px;
|
||||
.phone-header-black {
|
||||
height: 99px;
|
||||
border-radius: 20px 20px 0 0;
|
||||
background-size: 100%;
|
||||
.title {
|
||||
height: 99px;
|
||||
font-size: 16px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: end;
|
||||
padding-bottom: 13px;
|
||||
|
||||
.title-bar {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
.back {
|
||||
display: block;
|
||||
width: 50px;
|
||||
margin-left: 3px;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
.share {
|
||||
display: block;
|
||||
width: 50px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ad-bar {
|
||||
background-color: var(--grey-10);
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
}
|
||||
.phone-body-bg {
|
||||
padding: 0 16px;
|
||||
height: 680px;
|
||||
border-radius: 0 0 44px 44px;
|
||||
overflow: hidden;
|
||||
}
|
||||
.phone-body {
|
||||
width: 356px;
|
||||
margin-left: 17px;
|
||||
height: 630px;
|
||||
padding: 0;
|
||||
position: absolute;
|
||||
top: 98px;
|
||||
left: 0;
|
||||
z-index: 999;
|
||||
.comments {
|
||||
padding: 20px;
|
||||
word-break: break-all;
|
||||
}
|
||||
.form-data {
|
||||
padding: 10px 20px;
|
||||
.submit-btn {
|
||||
padding: 30px 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
.goods {
|
||||
.price {
|
||||
font-size: 18px;
|
||||
}
|
||||
.ele-cell-title {
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
.goods-attr {
|
||||
}
|
||||
}
|
||||
.goods-divider {
|
||||
height: 6px;
|
||||
}
|
||||
.buy-bar {
|
||||
position: fixed;
|
||||
width: 356px;
|
||||
bottom: 70px;
|
||||
//top: 881px;
|
||||
background-color: #ffffff;
|
||||
border-radius: 0 0 44px 44px;
|
||||
border-top: 1px solid var(--grey-8);
|
||||
.ele-cell-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
.home-btn,
|
||||
.shop-btn,
|
||||
.order-btn,
|
||||
.user-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
font-size: 13px;
|
||||
padding: 0 9px;
|
||||
cursor: pointer;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.icon {
|
||||
font-size: 19px;
|
||||
}
|
||||
.buy-btn {
|
||||
display: flex;
|
||||
.add-cart {
|
||||
border-radius: 100px 0 0 100px;
|
||||
border: none;
|
||||
background-color: var(--orange-5);
|
||||
color: #ffffff;
|
||||
height: 40px;
|
||||
width: 95px;
|
||||
}
|
||||
.buy-now {
|
||||
border-radius: 0 100px 100px 0;
|
||||
border: none;
|
||||
background-color: var(--red-6);
|
||||
color: #ffffff;
|
||||
height: 40px;
|
||||
width: 95px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.slick-slide) {
|
||||
overflow: hidden;
|
||||
}
|
||||
:deep(.slick-arrow.custom-slick-arrow) {
|
||||
font-size: 38px;
|
||||
}
|
||||
:deep(.slick-arrow.custom-slick-arrow) {
|
||||
color: #fff;
|
||||
background-color: rgba(31, 45, 61, 0.11);
|
||||
transition: ease all 0.3s;
|
||||
opacity: 0.3;
|
||||
z-index: 1;
|
||||
}
|
||||
:deep(.slick-arrow.custom-slick-arrow:before) {
|
||||
display: none;
|
||||
}
|
||||
:deep(.slick-arrow.custom-slick-arrow:hover) {
|
||||
color: #fff;
|
||||
opacity: 0.5;
|
||||
}
|
||||
:deep(.slick-slide h3) {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.user-card {
|
||||
height: 170px;
|
||||
margin: 0 1px;
|
||||
background-color: var(--grey-10);
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
display: flex;
|
||||
.user-avatar {
|
||||
margin-left: 16px;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
.user-info {
|
||||
margin-left: 10px;
|
||||
.nickname {
|
||||
color: var(--grey-3);
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.phone {
|
||||
color: var(--grey-5);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.menu-card {
|
||||
width: 330px;
|
||||
margin: 6px auto;
|
||||
border-radius: 5px;
|
||||
border-color: slategrey;
|
||||
.btn-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.merchant-card-title {
|
||||
width: 340px;
|
||||
margin: 6px auto;
|
||||
font-size: 20px;
|
||||
font-weight: 500;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.merchant-card {
|
||||
width: 340px;
|
||||
max-height: 80px;
|
||||
margin: 6px auto;
|
||||
margin-bottom: 16px;
|
||||
padding: 8px;
|
||||
background: #ffffff;
|
||||
border-radius: 5px;
|
||||
border-color: slategrey;
|
||||
.merchant-name {
|
||||
font-weight: 500;
|
||||
font-size: 15px;
|
||||
}
|
||||
.merchant-desc {
|
||||
width: 200px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
}
|
||||
|
||||
.tools-card {
|
||||
width: 340px;
|
||||
margin: 0 1px;
|
||||
padding: 6px 16px;
|
||||
background: #ffffff;
|
||||
border-radius: 5px;
|
||||
border-color: slategrey;
|
||||
position: absolute;
|
||||
top: 324px;
|
||||
left: 24px;
|
||||
.ele-cell {
|
||||
padding: 4px 0;
|
||||
border-bottom: 1px solid var(--grey-9);
|
||||
}
|
||||
.btn-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.order-card {
|
||||
width: 330px;
|
||||
height: 80px;
|
||||
margin: 0 auto;
|
||||
padding: 8px;
|
||||
background: #ffffff;
|
||||
border-radius: 5px;
|
||||
border-color: slategrey;
|
||||
//position: absolute;
|
||||
//top: 230px;
|
||||
//left: 24px;
|
||||
.btn-center {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
25
src/components/Tag/index.vue
Normal file
25
src/components/Tag/index.vue
Normal file
@@ -0,0 +1,25 @@
|
||||
<template>
|
||||
<template v-for="(item, index) in data" :key="index">
|
||||
<a-tag v-if="item.value == value" :color="item.comments">
|
||||
{{ item.label }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
placeholder?: string;
|
||||
dictCode?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: undefined
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = getDictionaryOptions(props.dictCode);
|
||||
</script>
|
||||
247
src/components/TinymceEditor/index.vue
Normal file
247
src/components/TinymceEditor/index.vue
Normal file
@@ -0,0 +1,247 @@
|
||||
<!-- 富文本编辑器 -->
|
||||
<template>
|
||||
<component v-if="inlineEditor" :is="tagName" :id="elementId" />
|
||||
<textarea v-else :id="elementId"></textarea>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
watch,
|
||||
onMounted,
|
||||
onBeforeUnmount,
|
||||
onActivated,
|
||||
onDeactivated,
|
||||
nextTick,
|
||||
useAttrs
|
||||
} from 'vue';
|
||||
import tinymce from 'tinymce/tinymce';
|
||||
import type {
|
||||
Editor as TinyMCEEditor,
|
||||
EditorEvent,
|
||||
RawEditorSettings
|
||||
} from 'tinymce';
|
||||
import 'tinymce/themes/silver';
|
||||
import 'tinymce/icons/default';
|
||||
import 'tinymce/plugins/code';
|
||||
import 'tinymce/plugins/preview';
|
||||
import 'tinymce/plugins/fullscreen';
|
||||
import 'tinymce/plugins/paste';
|
||||
import 'tinymce/plugins/searchreplace';
|
||||
import 'tinymce/plugins/save';
|
||||
import 'tinymce/plugins/autosave';
|
||||
import 'tinymce/plugins/link';
|
||||
import 'tinymce/plugins/autolink';
|
||||
import 'tinymce/plugins/image';
|
||||
import 'tinymce/plugins/media';
|
||||
import 'tinymce/plugins/table';
|
||||
import 'tinymce/plugins/codesample';
|
||||
import 'tinymce/plugins/lists';
|
||||
import 'tinymce/plugins/advlist';
|
||||
import 'tinymce/plugins/hr';
|
||||
import 'tinymce/plugins/charmap';
|
||||
import 'tinymce/plugins/emoticons';
|
||||
import 'tinymce/plugins/anchor';
|
||||
import 'tinymce/plugins/directionality';
|
||||
import 'tinymce/plugins/pagebreak';
|
||||
import 'tinymce/plugins/quickbars';
|
||||
import 'tinymce/plugins/nonbreaking';
|
||||
import 'tinymce/plugins/visualblocks';
|
||||
import 'tinymce/plugins/visualchars';
|
||||
import 'tinymce/plugins/wordcount';
|
||||
import 'tinymce/plugins/emoticons/js/emojis';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import {
|
||||
DEFAULT_CONFIG,
|
||||
DARK_CONFIG,
|
||||
uuid,
|
||||
bindHandlers,
|
||||
openAlert,
|
||||
setupTextIndent
|
||||
} from './util';
|
||||
import type { AlertOption } from './util';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
// 编辑器唯一 id
|
||||
id?: string;
|
||||
// v-model
|
||||
value?: string;
|
||||
// 编辑器配置
|
||||
init?: RawEditorSettings;
|
||||
// 是否内联模式
|
||||
inline?: boolean;
|
||||
// model events
|
||||
modelEvents?: string;
|
||||
// 内联模式标签名
|
||||
tagName?: string;
|
||||
// 是否禁用
|
||||
disabled?: boolean;
|
||||
// 是否跟随框架主题
|
||||
autoTheme?: boolean;
|
||||
// 不跟随框架主题时是否使用暗黑主题
|
||||
darkTheme?: boolean;
|
||||
}>(),
|
||||
{
|
||||
inline: false,
|
||||
modelEvents: 'change input undo redo',
|
||||
tagName: 'div',
|
||||
autoTheme: true
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
}>();
|
||||
|
||||
const attrs = useAttrs();
|
||||
const themeStore = useThemeStore();
|
||||
const { darkMode } = storeToRefs(themeStore);
|
||||
|
||||
// 编辑器唯一 id
|
||||
const elementId: string = props.id || uuid('tiny-vue');
|
||||
|
||||
// 编辑器实例
|
||||
let editorIns: TinyMCEEditor | null = null;
|
||||
|
||||
// 是否内联模式
|
||||
const inlineEditor: boolean = props.init?.inline || props.inline;
|
||||
|
||||
/* 更新 value */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
|
||||
/* 修改内容 */
|
||||
const setContent = (value?: string) => {
|
||||
if (
|
||||
editorIns &&
|
||||
typeof value === 'string' &&
|
||||
value !== editorIns.getContent()
|
||||
) {
|
||||
editorIns.setContent(value);
|
||||
}
|
||||
};
|
||||
|
||||
/* 渲染编辑器 */
|
||||
const render = () => {
|
||||
const isDark = props.autoTheme ? darkMode.value : props.darkTheme;
|
||||
tinymce.init({
|
||||
...DEFAULT_CONFIG,
|
||||
...(isDark ? DARK_CONFIG : {}),
|
||||
...props.init,
|
||||
selector: `#${elementId}`,
|
||||
readonly: props.disabled,
|
||||
inline: inlineEditor,
|
||||
setup: (editor: TinyMCEEditor) => {
|
||||
editorIns = editor;
|
||||
|
||||
// 添加首行缩进功能
|
||||
setupTextIndent(editor);
|
||||
|
||||
editor.on('init', (e: EditorEvent<any>) => {
|
||||
// 回显初始值
|
||||
if (props.value) {
|
||||
setContent(props.value);
|
||||
}
|
||||
// v-model
|
||||
editor.on(props.modelEvents, () => {
|
||||
updateValue(editor.getContent());
|
||||
});
|
||||
// valid events
|
||||
bindHandlers(e, attrs, editor);
|
||||
});
|
||||
if (typeof props.init?.setup === 'function') {
|
||||
props.init.setup(editor);
|
||||
}
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/* 销毁编辑器 */
|
||||
const destory = () => {
|
||||
if (tinymce != null && editorIns != null) {
|
||||
tinymce.remove(editorIns as any);
|
||||
editorIns = null;
|
||||
}
|
||||
};
|
||||
|
||||
/* 弹出提示框 */
|
||||
const alert = (option?: AlertOption) => {
|
||||
openAlert(editorIns, option);
|
||||
};
|
||||
|
||||
defineExpose({ editorIns, alert });
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(val: string, prevVal: string) => {
|
||||
if (val !== prevVal) {
|
||||
setContent(val);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.disabled,
|
||||
(disable) => {
|
||||
if (editorIns !== null) {
|
||||
if (typeof editorIns.mode?.set === 'function') {
|
||||
editorIns.mode.set(disable ? 'readonly' : 'design');
|
||||
} else {
|
||||
editorIns.setMode(disable ? 'readonly' : 'design');
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
watch(
|
||||
() => props.tagName,
|
||||
() => {
|
||||
destory();
|
||||
nextTick(() => {
|
||||
render();
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
watch(darkMode, () => {
|
||||
if (props.autoTheme) {
|
||||
destory();
|
||||
nextTick(() => {
|
||||
render();
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
destory();
|
||||
});
|
||||
|
||||
onActivated(() => {
|
||||
render();
|
||||
});
|
||||
|
||||
onDeactivated(() => {
|
||||
destory();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style>
|
||||
body .tox-tinymce-aux {
|
||||
z-index: 19990000;
|
||||
}
|
||||
|
||||
textarea[id^='tiny-vue'] {
|
||||
width: 0;
|
||||
height: 0;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
opacity: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
361
src/components/TinymceEditor/util.ts
Normal file
361
src/components/TinymceEditor/util.ts
Normal file
@@ -0,0 +1,361 @@
|
||||
import type {
|
||||
Editor as TinyMCEEditor,
|
||||
EditorEvent,
|
||||
RawEditorSettings
|
||||
} from 'tinymce';
|
||||
const BASE_URL = import.meta.env.BASE_URL;
|
||||
|
||||
// 默认加载插件
|
||||
const PLUGINS: string = [
|
||||
'code',
|
||||
'preview',
|
||||
'fullscreen',
|
||||
'paste',
|
||||
'searchreplace',
|
||||
'save',
|
||||
'autosave',
|
||||
'link',
|
||||
'autolink',
|
||||
'image',
|
||||
'media',
|
||||
'table',
|
||||
'codesample',
|
||||
'lists',
|
||||
'advlist',
|
||||
'hr',
|
||||
'charmap',
|
||||
'emoticons',
|
||||
'anchor',
|
||||
'directionality',
|
||||
'pagebreak',
|
||||
'quickbars',
|
||||
'nonbreaking',
|
||||
'visualblocks',
|
||||
'visualchars',
|
||||
'wordcount'
|
||||
].join(' ');
|
||||
|
||||
// 默认工具栏布局
|
||||
const TOOLBAR: string = [
|
||||
'fullscreen',
|
||||
'preview',
|
||||
'code',
|
||||
'codesample',
|
||||
'emoticons',
|
||||
'image',
|
||||
'media',
|
||||
'|',
|
||||
'undo',
|
||||
'redo',
|
||||
'|',
|
||||
'forecolor',
|
||||
'backcolor',
|
||||
'|',
|
||||
'bold',
|
||||
'italic',
|
||||
'underline',
|
||||
'strikethrough',
|
||||
'|',
|
||||
'alignleft',
|
||||
'aligncenter',
|
||||
'alignright',
|
||||
'alignjustify',
|
||||
'|',
|
||||
'outdent',
|
||||
'indent',
|
||||
'textindent',
|
||||
'|',
|
||||
'numlist',
|
||||
'bullist',
|
||||
'|',
|
||||
'formatselect',
|
||||
'fontselect',
|
||||
'fontsizeselect',
|
||||
'|',
|
||||
'link',
|
||||
'charmap',
|
||||
'anchor',
|
||||
'pagebreak',
|
||||
'|',
|
||||
'ltr',
|
||||
'rtl'
|
||||
].join(' ');
|
||||
|
||||
// 默认配置
|
||||
export const DEFAULT_CONFIG: RawEditorSettings = {
|
||||
height: 300,
|
||||
branding: false,
|
||||
skin_url: BASE_URL + 'tinymce/skins/ui/oxide',
|
||||
content_css: BASE_URL + 'tinymce/skins/content/default/content.min.css',
|
||||
language_url: BASE_URL + 'tinymce/langs/zh_CN.js',
|
||||
language: 'zh_CN',
|
||||
plugins: PLUGINS,
|
||||
toolbar: TOOLBAR,
|
||||
draggable_modal: true,
|
||||
toolbar_mode: 'sliding',
|
||||
quickbars_insert_toolbar: '',
|
||||
images_upload_handler: (blobInfo: any, success: any, error: any) => {
|
||||
if (blobInfo.blob().size / 1024 > 400) {
|
||||
error('大小不能超过 400KB');
|
||||
return;
|
||||
}
|
||||
success('data:image/jpeg;base64,' + blobInfo.base64());
|
||||
},
|
||||
file_picker_types: 'media',
|
||||
file_picker_callback: () => {},
|
||||
|
||||
// 添加自定义样式
|
||||
content_style: `
|
||||
.text-indent { text-indent: 2em; }
|
||||
p.text-indent { text-indent: 2em; }
|
||||
p[style*="text-indent"] {
|
||||
text-indent: 2em;
|
||||
}
|
||||
/* 确保首行缩进在各种情况下都能正确显示 */
|
||||
body p {
|
||||
margin: 0 0 10px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
`
|
||||
};
|
||||
|
||||
// 暗黑主题配置
|
||||
export const DARK_CONFIG: RawEditorSettings = {
|
||||
skin_url: BASE_URL + 'tinymce/skins/ui/oxide-dark',
|
||||
content_css: BASE_URL + 'tinymce/skins/content/dark/content.min.css'
|
||||
};
|
||||
|
||||
// 支持监听的事件
|
||||
export const VALID_EVENTS = [
|
||||
'onActivate',
|
||||
'onAddUndo',
|
||||
'onBeforeAddUndo',
|
||||
'onBeforeExecCommand',
|
||||
'onBeforeGetContent',
|
||||
'onBeforeRenderUI',
|
||||
'onBeforeSetContent',
|
||||
'onBeforePaste',
|
||||
'onBlur',
|
||||
'onChange',
|
||||
'onClearUndos',
|
||||
'onClick',
|
||||
'onContextMenu',
|
||||
'onCopy',
|
||||
'onCut',
|
||||
'onDblclick',
|
||||
'onDeactivate',
|
||||
'onDirty',
|
||||
'onDrag',
|
||||
'onDragDrop',
|
||||
'onDragEnd',
|
||||
'onDragGesture',
|
||||
'onDragOver',
|
||||
'onDrop',
|
||||
'onExecCommand',
|
||||
'onFocus',
|
||||
'onFocusIn',
|
||||
'onFocusOut',
|
||||
'onGetContent',
|
||||
'onHide',
|
||||
'onInit',
|
||||
'onKeyDown',
|
||||
'onKeyPress',
|
||||
'onKeyUp',
|
||||
'onLoadContent',
|
||||
'onMouseDown',
|
||||
'onMouseEnter',
|
||||
'onMouseLeave',
|
||||
'onMouseMove',
|
||||
'onMouseOut',
|
||||
'onMouseOver',
|
||||
'onMouseUp',
|
||||
'onNodeChange',
|
||||
'onObjectResizeStart',
|
||||
'onObjectResized',
|
||||
'onObjectSelected',
|
||||
'onPaste',
|
||||
'onPostProcess',
|
||||
'onPostRender',
|
||||
'onPreProcess',
|
||||
'onProgressState',
|
||||
'onRedo',
|
||||
'onRemove',
|
||||
'onReset',
|
||||
'onSaveContent',
|
||||
'onSelectionChange',
|
||||
'onSetAttrib',
|
||||
'onSetContent',
|
||||
'onShow',
|
||||
'onSubmit',
|
||||
'onUndo',
|
||||
'onVisualAid'
|
||||
];
|
||||
|
||||
let unique = 0;
|
||||
|
||||
/**
|
||||
* 生成编辑器 id
|
||||
*/
|
||||
export function uuid(prefix: string): string {
|
||||
const time = Date.now();
|
||||
const random = Math.floor(Math.random() * 1000000000);
|
||||
unique++;
|
||||
return prefix + '_' + random + unique + String(time);
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定事件
|
||||
*/
|
||||
export function bindHandlers(
|
||||
initEvent: EditorEvent<any>,
|
||||
listeners: Record<string, any>,
|
||||
editor: TinyMCEEditor
|
||||
): void {
|
||||
const validEvents = VALID_EVENTS.map((event) => event.toLowerCase());
|
||||
Object.keys(listeners)
|
||||
.filter((key: string) => validEvents.includes(key.toLowerCase()))
|
||||
.forEach((key: string) => {
|
||||
const handler = listeners[key];
|
||||
if (typeof handler === 'function') {
|
||||
if (key === 'onInit') {
|
||||
handler(initEvent, editor);
|
||||
} else {
|
||||
editor.on(key.substring(2), (e: EditorEvent<any>) =>
|
||||
handler(e, editor)
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 弹出提示框
|
||||
*/
|
||||
export function openAlert(
|
||||
editor: TinyMCEEditor | null,
|
||||
option: AlertOption = {}
|
||||
) {
|
||||
editor?.windowManager?.open({
|
||||
title: option.title ?? '提示',
|
||||
body: {
|
||||
type: 'panel',
|
||||
items: [
|
||||
{
|
||||
type: 'htmlpanel',
|
||||
html: `<p>${option.content ?? ''}</p>`
|
||||
}
|
||||
]
|
||||
},
|
||||
buttons: [
|
||||
{
|
||||
type: 'cancel',
|
||||
name: 'closeButton',
|
||||
text: '确定',
|
||||
primary: true
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
|
||||
export interface AlertOption {
|
||||
title?: string;
|
||||
content?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加首行缩进功能
|
||||
*/
|
||||
export function setupTextIndent(editor: TinyMCEEditor) {
|
||||
// 注册首行缩进按钮
|
||||
editor.ui.registry.addButton('textindent', {
|
||||
icon: 'indent',
|
||||
tooltip: '段落首行缩进 (Ctrl+Shift+I)',
|
||||
onAction: () => {
|
||||
const selection = editor.selection;
|
||||
const selectedNode = selection.getNode();
|
||||
|
||||
// 获取当前段落元素
|
||||
let paragraph = selectedNode;
|
||||
while (
|
||||
paragraph &&
|
||||
paragraph.nodeName !== 'P' &&
|
||||
paragraph.nodeName !== 'DIV'
|
||||
) {
|
||||
paragraph = paragraph.parentNode as Element;
|
||||
}
|
||||
|
||||
if (
|
||||
paragraph &&
|
||||
(paragraph.nodeName === 'P' || paragraph.nodeName === 'DIV')
|
||||
) {
|
||||
const currentIndent = (paragraph as HTMLElement).style.textIndent;
|
||||
|
||||
if (currentIndent === '2em' || currentIndent === '32px') {
|
||||
// 如果已经有首行缩进,则取消
|
||||
(paragraph as HTMLElement).style.textIndent = '';
|
||||
editor.notificationManager.open({
|
||||
text: '已取消段落首行缩进',
|
||||
type: 'info',
|
||||
timeout: 2000
|
||||
});
|
||||
} else {
|
||||
// 添加首行缩进
|
||||
(paragraph as HTMLElement).style.textIndent = '2em';
|
||||
editor.notificationManager.open({
|
||||
text: '已应用段落首行缩进',
|
||||
type: 'success',
|
||||
timeout: 2000
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// 如果没有选中段落,则对当前行应用首行缩进
|
||||
const content = selection.getContent();
|
||||
if (content) {
|
||||
// 如果有选择内容,将选择的内容包装在带缩进的段落中
|
||||
editor.execCommand(
|
||||
'mceInsertContent',
|
||||
false,
|
||||
`<p style="text-indent: 2em;">${content}</p>`
|
||||
);
|
||||
} else {
|
||||
// 如果没有选择内容,在当前位置插入带缩进的段落
|
||||
editor.execCommand('FormatBlock', false, 'p');
|
||||
const newParagraph = selection.getNode();
|
||||
if (newParagraph && newParagraph.nodeName === 'P') {
|
||||
(newParagraph as HTMLElement).style.textIndent = '2em';
|
||||
}
|
||||
}
|
||||
editor.notificationManager.open({
|
||||
text: '已应用段落首行缩进',
|
||||
type: 'success',
|
||||
timeout: 2000
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// 注册快捷键
|
||||
editor.addShortcut('ctrl+shift+i', '段落首行缩进', () => {
|
||||
// 触发按钮点击事件
|
||||
const button = editor.ui.registry.getAll().buttons.textindent;
|
||||
// @ts-ignore
|
||||
if (button && button.onAction) {
|
||||
// @ts-ignore
|
||||
button.onAction();
|
||||
}
|
||||
});
|
||||
|
||||
// 添加菜单项
|
||||
editor.ui.registry.addMenuItem('textindent', {
|
||||
text: '首行缩进',
|
||||
icon: 'indent',
|
||||
onAction: () => {
|
||||
const button = editor.ui.registry.getAll().buttons.textindent;
|
||||
// @ts-ignore
|
||||
if (button && button.onAction) {
|
||||
// @ts-ignore
|
||||
button.onAction();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
87
src/components/UploadCert/index.vue
Normal file
87
src/components/UploadCert/index.vue
Normal file
@@ -0,0 +1,87 @@
|
||||
<!-- 文件上传组件 -->
|
||||
<template>
|
||||
<a-upload
|
||||
:accept="accept"
|
||||
:maxCount="maxCount"
|
||||
:showUploadList="showUploadList"
|
||||
:customRequest="onUpload"
|
||||
>
|
||||
<a-button class="ele-btn-icon">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
<span>{{ buttonText }}</span>
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { UploadOutlined } from '@ant-design/icons-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { uploadCert } from '@/api/system/file';
|
||||
import { messageLoading } from 'ele-admin-pro';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
limit?: number;
|
||||
maxCount?: number | 1;
|
||||
accept?: string;
|
||||
placeholder?: string;
|
||||
buttonText?: string;
|
||||
showUploadList?: boolean;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择上传文件',
|
||||
buttonText: '上传文件',
|
||||
showUploadList: false
|
||||
}
|
||||
);
|
||||
|
||||
// 已上传数据
|
||||
// const images = ref<ItemType[]>([]);
|
||||
//
|
||||
// const onChange = (type) => {
|
||||
// console.log(type, '>>>>>');
|
||||
// };
|
||||
|
||||
// 上传文件
|
||||
const onUpload = (item) => {
|
||||
const { file } = item;
|
||||
if (file.size / 1024 / 1024 > 100) {
|
||||
message.error('大小不能超过 100MB');
|
||||
return;
|
||||
}
|
||||
const hide = messageLoading({
|
||||
content: '上传中..',
|
||||
duration: 0,
|
||||
mask: true
|
||||
});
|
||||
uploadCert(file)
|
||||
.then((data) => {
|
||||
hide();
|
||||
console.log(data, '上传文件成功!');
|
||||
emit('update:value', String(data.path));
|
||||
message.success('上传成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
hide();
|
||||
});
|
||||
};
|
||||
|
||||
// /* 更新选中数据 */
|
||||
// const updateValue = (value: string) => {
|
||||
// console.log(value, '更新选中数据');
|
||||
// emit('update:value', value + '更新选中数据');
|
||||
// };
|
||||
/* 失去焦点 */
|
||||
// const onBlur = () => {
|
||||
// emit('blur');
|
||||
// };
|
||||
</script>
|
||||
142
src/components/UploadFile/index.vue
Normal file
142
src/components/UploadFile/index.vue
Normal file
@@ -0,0 +1,142 @@
|
||||
<!-- 文件上传组件 -->
|
||||
<template>
|
||||
<a-upload
|
||||
:accept="accept"
|
||||
:maxCount="maxCount"
|
||||
:showUploadList="showUploadList"
|
||||
:customRequest="onUpload"
|
||||
:before-upload="beforeUpload"
|
||||
@remove="handleRemove"
|
||||
:file-list="fileList"
|
||||
>
|
||||
<a-button class="ele-btn-icon">
|
||||
<template #icon>
|
||||
<UploadOutlined />
|
||||
</template>
|
||||
<span>{{ buttonText }}</span>
|
||||
</a-button>
|
||||
</a-upload>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { UploadOutlined } from '@ant-design/icons-vue';
|
||||
import { message, UploadProps } from 'ant-design-vue';
|
||||
import { uploadFile, uploadOss } from '@/api/system/file';
|
||||
import { messageLoading } from 'ele-admin-pro';
|
||||
import { ref, watch } from 'vue';
|
||||
import { uploadAliOss } from '@/utils/oss';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value: string): void;
|
||||
(e: 'blur'): void;
|
||||
}>();
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: string;
|
||||
limit?: number;
|
||||
maxCount?: number | 1;
|
||||
accept?: string;
|
||||
placeholder?: string;
|
||||
buttonText?: string;
|
||||
showUploadList?: boolean;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择上传文件',
|
||||
buttonText: '上传文件',
|
||||
showUploadList: false
|
||||
}
|
||||
);
|
||||
|
||||
// 已上传数据
|
||||
// const images = ref<ItemType[]>([]);
|
||||
//
|
||||
// const onChange = (type) => {
|
||||
// console.log(type, '>>>>>');
|
||||
// };
|
||||
|
||||
const fileList = ref<UploadProps['fileList']>([]);
|
||||
const uploading = ref<boolean>(false);
|
||||
const beforeUpload: UploadProps['beforeUpload'] = (file) => {
|
||||
// fileList.value = [...fileList.value, file];
|
||||
if (file.size / 1024 / 1024 > 1000) {
|
||||
message.error('大小不能超过 1000MB');
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileList.value.length >= 1) {
|
||||
message.error('不支持多文件');
|
||||
return;
|
||||
}
|
||||
const hide = messageLoading({
|
||||
content: '上传中..',
|
||||
duration: 0,
|
||||
mask: true
|
||||
});
|
||||
|
||||
if (Number(localStorage.getItem('TenantId')) == 10058) {
|
||||
uploadAliOss(file)
|
||||
.then((res) => {
|
||||
hide();
|
||||
emit('update:value', String(res.url));
|
||||
message.success('上传成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
} else {
|
||||
uploadOss(file)
|
||||
.then((data) => {
|
||||
hide();
|
||||
console.log(data, '上传文件成功!');
|
||||
|
||||
emit('update:value', String(data.path));
|
||||
message.success('上传成功');
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
hide();
|
||||
});
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
const handleRemove: UploadProps['onRemove'] = (file) => {
|
||||
const index = fileList.value.indexOf(file);
|
||||
const newFileList = fileList.value.slice();
|
||||
newFileList.splice(index, 1);
|
||||
fileList.value = newFileList;
|
||||
emit('update:value', null);
|
||||
};
|
||||
// 上传文件
|
||||
const onUpload = (item) => {
|
||||
const { file } = item;
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(newVal, oldValue) => {
|
||||
if (newVal) {
|
||||
fileList.value.push({
|
||||
uid: newVal,
|
||||
url: newVal,
|
||||
name: newVal,
|
||||
status: 'done'
|
||||
});
|
||||
}
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
|
||||
// /* 更新选中数据 */
|
||||
// const updateValue = (value: string) => {
|
||||
// console.log(value, '更新选中数据');
|
||||
// emit('update:value', value + '更新选中数据');
|
||||
// };
|
||||
/* 失去焦点 */
|
||||
// const onBlur = () => {
|
||||
// emit('blur');
|
||||
// };
|
||||
</script>
|
||||
65
src/components/User/index.vue
Normal file
65
src/components/User/index.vue
Normal file
@@ -0,0 +1,65 @@
|
||||
<template>
|
||||
<a-popover>
|
||||
<template #content>
|
||||
<div class="user-box">
|
||||
<div class="user-info">
|
||||
<span class="ele-text-secondary">ID:{{ record.userId }} </span>
|
||||
<span class="ele-text-secondary">账号:{{ record.username }} </span>
|
||||
<span class="ele-text-secondary" v-if="record.alias">
|
||||
别名:{{ record.alias }}
|
||||
</span>
|
||||
<span class="ele-text-secondary">昵称:{{ record.nickname }}</span>
|
||||
<span class="ele-text-secondary" v-if="record.realName"
|
||||
>姓名:{{ record.realName }}
|
||||
</span>
|
||||
<span class="ele-text-secondary">手机号:{{ record.phone }}</span>
|
||||
<span class="ele-text-secondary">邮箱:{{ record.email }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div class="user-box">
|
||||
<a-avatar v-if="record.avatar" :size="32" :src="`${record.avatar}`">
|
||||
<template #icon>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
</a-avatar>
|
||||
<div class="user-info">
|
||||
<router-link :to="'/system/user/details?id=' + record.userId">
|
||||
<span class="ele-text-primary">
|
||||
{{ record.alias ? record.alias : record.nickname }}
|
||||
</span>
|
||||
</router-link>
|
||||
<span class="ele-text-placeholder">{{ record.phone }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-popover>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { User } from '@/api/system/user/model';
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
placeholder?: string;
|
||||
record?: User | object;
|
||||
}>(),
|
||||
{
|
||||
placeholder: undefined
|
||||
}
|
||||
);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.user-box {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
.user-info {
|
||||
padding-left: 5px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: start;
|
||||
.nickname {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
37
src/components/UserChoose/choose-search.vue
Normal file
37
src/components/UserChoose/choose-search.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div style="max-width: 240px">
|
||||
<a-input
|
||||
allow-clear
|
||||
v-model:value="where.phone"
|
||||
placeholder="输入手机号码搜素"
|
||||
prefix-icon="el-icon-search"
|
||||
@change="search"
|
||||
>
|
||||
<template #prefix>
|
||||
<SearchOutlined class="ele-text-placeholder" />
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue';
|
||||
import { SearchOutlined } from '@ant-design/icons-vue';
|
||||
import type { WhereType } from './types';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where: WhereType): void;
|
||||
}>();
|
||||
|
||||
// 搜索表单
|
||||
const where = reactive<WhereType>({
|
||||
keywords: '',
|
||||
nickname: '',
|
||||
phone: ''
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', where);
|
||||
};
|
||||
</script>
|
||||
146
src/components/UserChoose/index.vue
Normal file
146
src/components/UserChoose/index.vue
Normal file
@@ -0,0 +1,146 @@
|
||||
<template>
|
||||
<ele-table-select
|
||||
ref="selectRef"
|
||||
:allow-clear="true"
|
||||
:placeholder="placeholder"
|
||||
v-model:value="selectedValue"
|
||||
value-key="userId"
|
||||
label-key="nickname"
|
||||
:table-config="tableConfig"
|
||||
:overlay-style="{ width: '520px', maxWidth: '80%' }"
|
||||
:init-value="initValue"
|
||||
@select="onSelect"
|
||||
@clear="onClear"
|
||||
>
|
||||
<!-- 角色列 -->
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'nickname'">
|
||||
<a-tooltip :title="`ID:${record.userId}`">
|
||||
<a-avatar
|
||||
:size="30"
|
||||
:src="`${record.avatar}`"
|
||||
style="margin-right: 4px"
|
||||
>
|
||||
<template #icon>
|
||||
<UserOutlined />
|
||||
</template>
|
||||
</a-avatar>
|
||||
{{ record.nickname }}
|
||||
</a-tooltip>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'roles'">
|
||||
<a-tag v-for="item in record.roles" :key="item.roleId" color="blue">
|
||||
{{ item.roleName }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</template>
|
||||
<!-- 表头工具栏 -->
|
||||
<template #toolbar>
|
||||
<ChooseSearch @search="search" />
|
||||
</template>
|
||||
</ele-table-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import ChooseSearch from './choose-search.vue';
|
||||
import type { EleTableSelect } from 'ele-admin-pro/es';
|
||||
import type { ProTableProps } from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import type { WhereType } from './types';
|
||||
import { pageUsers, getUser } from '@/api/system/user';
|
||||
import type { User } from '@/api/user/model';
|
||||
import { UserOutlined } from '@ant-design/icons-vue';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: number | 0;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择用户'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'select', where?: User): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
const selectedValue = ref<number>();
|
||||
// 回显值
|
||||
const initValue = ref<User | null>(null);
|
||||
// 选择框实例
|
||||
const selectRef = ref<InstanceType<typeof EleTableSelect> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const tableConfig = reactive<ProTableProps>({
|
||||
datasource: ({ page, limit, where, orders }) => {
|
||||
return pageUsers({ ...where, ...orders, page, limit });
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'nickname',
|
||||
key: 'nickname',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'phone',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '角色',
|
||||
dataIndex: 'roles',
|
||||
key: 'roles',
|
||||
showSorterTooltip: false
|
||||
}
|
||||
],
|
||||
toolkit: ['reload', 'columns'],
|
||||
size: 'small',
|
||||
pageSize: 6,
|
||||
toolStyle: {
|
||||
padding: '0 8px'
|
||||
}
|
||||
});
|
||||
|
||||
// 搜索
|
||||
const search = (where: WhereType) => {
|
||||
selectRef.value?.reload({
|
||||
where,
|
||||
page: 1
|
||||
});
|
||||
};
|
||||
|
||||
const onClear = () => {
|
||||
emit('clear');
|
||||
};
|
||||
|
||||
const onSelect = (item) => {
|
||||
emit('select', item);
|
||||
};
|
||||
|
||||
/* 回显数据 */
|
||||
const setInitValue = () => {
|
||||
const userId = Number(props.value);
|
||||
if (!userId) {
|
||||
return;
|
||||
}
|
||||
getUser(userId).then((res) => {
|
||||
if (res) {
|
||||
initValue.value = res;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
setInitValue();
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(value) => {
|
||||
if (value) {
|
||||
setInitValue();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
9
src/components/UserChoose/types/index.ts
Normal file
9
src/components/UserChoose/types/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 搜索表单类型
|
||||
*/
|
||||
export interface WhereType {
|
||||
keywords?: string;
|
||||
nickname?: string;
|
||||
userId?: number;
|
||||
phone?: string;
|
||||
}
|
||||
144
src/components/UserSelect/index.vue
Normal file
144
src/components/UserSelect/index.vue
Normal file
@@ -0,0 +1,144 @@
|
||||
<template>
|
||||
<ele-table-select
|
||||
ref="selectRef"
|
||||
:allow-clear="true"
|
||||
:placeholder="placeholder"
|
||||
v-model:value="selectedValue"
|
||||
value-key="userId"
|
||||
label-key="nickname"
|
||||
:table-config="tableConfig"
|
||||
:overlay-style="{ width: '520px', maxWidth: '80%' }"
|
||||
:init-value="initValue"
|
||||
@select="onSelect"
|
||||
@clear="onClear"
|
||||
>
|
||||
<!-- 角色列 -->
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'progress'">
|
||||
<div v-for="(d, i) in progressDict" :key="i">
|
||||
<span v-if="d.value === record.progress">{{ d.label }}</span>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="column.key === 'nickname'">
|
||||
<a-avatar
|
||||
:size="30"
|
||||
:src="`${record.avatar}`"
|
||||
style="margin-right: 4px"
|
||||
/>
|
||||
{{ record.nickname }}
|
||||
</template>
|
||||
</template>
|
||||
<!-- 表头工具栏 -->
|
||||
<template #toolbar>
|
||||
<UserSearch @search="search" />
|
||||
</template>
|
||||
</ele-table-select>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import UserSearch from './user-search.vue';
|
||||
import type { EleTableSelect } from 'ele-admin-pro/es';
|
||||
import type { ProTableProps } from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import type { WhereType } from './types';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
import type { User } from '@/api/user/model';
|
||||
import { isArray } from 'lodash-es';
|
||||
|
||||
const props = withDefaults(
|
||||
defineProps<{
|
||||
value?: number | 0;
|
||||
placeholder?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择用户'
|
||||
}
|
||||
);
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'select', where?: User): void;
|
||||
(e: 'clear'): void;
|
||||
}>();
|
||||
|
||||
const selectedValue = ref<number>();
|
||||
// 获取字典数据
|
||||
const progressDict = getDictionaryOptions('userFollowStatus');
|
||||
// 回显值
|
||||
const initValue = ref<User[]>([]);
|
||||
// 选择框实例
|
||||
const selectRef = ref<InstanceType<typeof EleTableSelect> | null>(null);
|
||||
|
||||
// 表格配置
|
||||
const tableConfig = reactive<ProTableProps>({
|
||||
datasource: ({ page, limit, where, orders }) => {
|
||||
return pageUsers({ ...where, ...orders, page, limit });
|
||||
},
|
||||
columns: [
|
||||
{
|
||||
title: '昵称',
|
||||
dataIndex: 'nickname',
|
||||
key: 'nickname',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '手机号码',
|
||||
dataIndex: 'phone',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '部门',
|
||||
dataIndex: 'organizationName',
|
||||
showSorterTooltip: false
|
||||
}
|
||||
],
|
||||
toolkit: ['reload', 'columns'],
|
||||
size: 'small',
|
||||
pageSize: 6,
|
||||
toolStyle: {
|
||||
padding: '0 8px'
|
||||
}
|
||||
});
|
||||
|
||||
// 搜索
|
||||
const search = (where: WhereType) => {
|
||||
selectRef.value?.reload({
|
||||
where,
|
||||
page: 1
|
||||
});
|
||||
};
|
||||
|
||||
const onClear = () => {
|
||||
emit('clear');
|
||||
};
|
||||
|
||||
const onSelect = (item) => {
|
||||
emit('select', item);
|
||||
};
|
||||
|
||||
/* 回显数据 */
|
||||
const setInitValue = () => {
|
||||
if (isArray(props.value)) {
|
||||
initValue.value = props.value;
|
||||
}
|
||||
const userId = Number(props.value);
|
||||
if (userId) {
|
||||
}
|
||||
// getUser(userId).then((res) => {
|
||||
// if (res) {
|
||||
// initValue.value = res;
|
||||
// }
|
||||
// });
|
||||
};
|
||||
|
||||
setInitValue();
|
||||
|
||||
watch(
|
||||
() => props.value,
|
||||
(value) => {
|
||||
if (value) {
|
||||
setInitValue();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
9
src/components/UserSelect/types/index.ts
Normal file
9
src/components/UserSelect/types/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
/**
|
||||
* 搜索表单类型
|
||||
*/
|
||||
export interface WhereType {
|
||||
keywords?: string;
|
||||
nickname?: string;
|
||||
userId?: number;
|
||||
phone?: string;
|
||||
}
|
||||
37
src/components/UserSelect/user-search.vue
Normal file
37
src/components/UserSelect/user-search.vue
Normal file
@@ -0,0 +1,37 @@
|
||||
<template>
|
||||
<div style="max-width: 240px">
|
||||
<a-input
|
||||
allow-clear
|
||||
v-model:value="where.nickname"
|
||||
placeholder="输入用户昵称"
|
||||
prefix-icon="el-icon-search"
|
||||
@change="search"
|
||||
>
|
||||
<template #prefix>
|
||||
<SearchOutlined class="ele-text-placeholder" />
|
||||
</template>
|
||||
</a-input>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { reactive } from 'vue';
|
||||
import { SearchOutlined } from '@ant-design/icons-vue';
|
||||
import type { WhereType } from './types';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'search', where: WhereType): void;
|
||||
}>();
|
||||
|
||||
// 搜索表单
|
||||
const where = reactive<WhereType>({
|
||||
keywords: '',
|
||||
nickname: '',
|
||||
phone: ''
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
emit('search', where);
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user