Initial commit

This commit is contained in:
南宁网宿科技
2024-04-24 16:36:46 +08:00
commit 121348e011
991 changed files with 158700 additions and 0 deletions

View 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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,256 @@
<template>
<!-- 显示工具栏 -->
<template v-if="visible">
<template v-if="data">
<!-- 首页才显示 -->
<a-card
v-if="data.home == 1"
title="页面设置"
size="small"
:bordered="false"
style="width: 300px"
>
<template #extra>
<a-button v-if="visible" type="text">
<template #icon>
<MinusOutlined @click="openEdit" />
</template>
</a-button>
</template>
<a-form
v-if="visible"
ref="formRef"
layout="vertical"
:model="form"
:rules="rules"
>
{{ layout }}
<a-form-item label="画板">
<a-descriptions :column="2">
<a-descriptions-item
label="内容区域"
:labelStyle="{ width: '70px', alignItems: 'center' }"
>
<a-input
placeholder="1200"
v-model:value="data.layout"
style="width: 90px"
/>
</a-descriptions-item>
</a-descriptions>
</a-form-item>
<a-form-item label="颜色">
<a-descriptions :column="2">
<a-descriptions-item
label="背景颜色"
:labelStyle="{ width: '70px', alignItems: 'center' }"
>
<ele-color-picker
v-model:value="layout.backgroundColor"
@change="updateColor"
/>
</a-descriptions-item>
<a-descriptions-item
label="文字颜色"
:labelStyle="{ width: '70px', alignItems: 'center' }"
>
<ele-color-picker
v-model:value="layout.color"
@change="updateColor"
/>
</a-descriptions-item>
<a-descriptions-item
label="高亮颜色"
:labelStyle="{ width: '70px', alignItems: 'center' }"
>
<ele-color-picker
v-model:value="layout.color"
@change="updateColor"
/>
</a-descriptions-item>
</a-descriptions>
</a-form-item>
</a-form>
</a-card>
<a-card
v-if="data.home == 0"
title="数据面板"
size="small"
:bordered="false"
style="width: 300px"
>
<template #extra>
<a-button v-if="visible" type="text">
<template #icon>
<MinusOutlined @click="openEdit" />
</template>
</a-button>
</template>
<a-form
v-if="visible"
ref="formRef"
layout="vertical"
:model="form"
:rules="rules"
>
<template v-if="item">
<a-form-item label="画板">
<a-descriptions :column="2">
<a-descriptions-item
label="X"
:labelStyle="{ width: '25px', alignItems: 'center' }"
>
<a-input-number
placeholder="X"
style="width: 90px"
v-model:value="item.style.marginLeft"
/>
</a-descriptions-item>
<a-descriptions-item
label="X"
:labelStyle="{ width: '25px', alignItems: 'center' }"
>
<a-input-number
placeholder="X"
style="width: 90px"
v-model:value="item.style.marginTop"
/>
</a-descriptions-item>
<a-descriptions-item
label="W"
:labelStyle="{ width: '25px', alignItems: 'center' }"
>
<a-input
placeholder="W"
v-model:value="item.style.width"
style="width: 90px"
/>
</a-descriptions-item>
<a-descriptions-item
label="H"
:labelStyle="{ width: '25px', alignItems: 'center' }"
>
<a-input-number
placeholder=""
style="width: 90px"
v-model:value="item.style.height"
/>
</a-descriptions-item>
<a-descriptions-item
label="R"
:labelStyle="{ width: '25px', alignItems: 'center' }"
>
<a-input-number
placeholder="圆角半径"
style="width: 90px"
v-model:value="item.style.borderRadius"
/>
</a-descriptions-item>
</a-descriptions>
</a-form-item>
<a-form-item label="背景颜色">
<ele-color-picker
v-model:value="item.style.backgroundColor"
@change="updateColor"
/>
</a-form-item>
</template>
</a-form>
</a-card>
</template>
</template>
<!-- 隐藏工具栏 -->
<a-button v-if="!visible" type="text" @click="openEdit">
<template #icon>
<SettingOutlined />
</template>
</a-button>
</template>
<script setup lang="ts">
import useFormData from '@/utils/use-form-data';
import { reactive, ref, watch } from 'vue';
import { MinusOutlined, SettingOutlined } from '@ant-design/icons-vue';
import { Design } from '@/api/cms/design/model';
import { Layout } from '@/api/layout/model';
const props = defineProps<{
// 是否显示
visible: boolean;
// 修改回显的数据
data?: Design | null;
layout?: Layout;
index?: number | 0;
}>();
const item = ref<any>({
title: '页面设置',
name: 'common',
style: {
width: '100vw',
height: '100vh',
borderRadius: '4'
},
data: []
});
const emit = defineEmits<{
(e: 'done', index: number): void;
(e: 'update'): void;
}>();
// 表单数据
const { form, assignFields } = useFormData<Design>({
pageId: undefined,
home: undefined,
layout: undefined
});
// 表单验证规则
const rules = reactive({
name: [
{
required: true,
type: 'string',
message: '请填写页面名称',
trigger: 'blur'
}
],
path: [
{
required: true,
type: 'string',
message: '请填写路由地址',
trigger: 'blur'
}
],
component: [
{
required: true,
type: 'string',
message: '请填写组件路径',
trigger: 'blur'
}
]
});
const openEdit = () => {
emit('update');
};
const updateColor = (color: string) => {
item.value.style.background = color;
emit('done', item);
};
const onClick = () => {
emit('done', Number(props.index));
};
watch(
() => props.data,
(data) => {
console.log(data);
}
);
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,171 @@
<template>
<!-- 显示工具栏 -->
<template v-if="visible">
<template v-if="data">
<!-- 首页才显示 -->
<a-card
v-if="data.home == 1"
title="页面设置"
size="small"
:bordered="false"
style="width: 300px"
>
<template #extra>
<a-button v-if="visible" type="text">
<template #icon>
<MinusOutlined @click="openEdit" />
</template>
</a-button>
</template>
<a-form
v-if="visible"
ref="formRef"
layout="vertical"
:model="form"
:rules="rules"
>
{{ layout }}
<a-form-item label="画板">
<a-descriptions :column="2">
<a-descriptions-item
label="内容区域"
:labelStyle="{ width: '70px', alignItems: 'center' }"
>
<a-input
placeholder="1200"
v-model:value="layout.width"
style="width: 90px"
/>
</a-descriptions-item>
</a-descriptions>
</a-form-item>
<a-form-item label="颜色">
<a-descriptions :column="2">
<a-descriptions-item
label="背景颜色"
:labelStyle="{ width: '70px', alignItems: 'center' }"
>
<ele-color-picker
v-model:value="data.layout.backgroundColor"
@change="updateColor"
/>
</a-descriptions-item>
<a-descriptions-item
label="文字颜色"
:labelStyle="{ width: '70px', alignItems: 'center' }"
>
<ele-color-picker
v-model:value="layout.color"
@change="updateColor"
/>
</a-descriptions-item>
<a-descriptions-item
label="高亮颜色"
:labelStyle="{ width: '70px', alignItems: 'center' }"
>
<ele-color-picker
v-model:value="layout.hover"
@change="updateColor"
/>
</a-descriptions-item>
</a-descriptions>
</a-form-item>
</a-form>
</a-card>
</template>
</template>
<!-- 隐藏工具栏 -->
<a-button v-if="!visible" type="text" @click="openEdit">
<template #icon>
<SettingOutlined />
</template>
</a-button>
</template>
<script setup lang="ts">
import useFormData from '@/utils/use-form-data';
import { reactive, ref, watch } from 'vue';
import { MinusOutlined, SettingOutlined } from '@ant-design/icons-vue';
import { Design } from '@/api/cms/design/model';
import { Layout } from '@/api/layout/model';
const layout = ref<Layout>({});
const props = defineProps<{
// 是否显示
visible: boolean;
// 修改回显的数据
data?: Design | null;
}>();
const item = ref<any>({
title: '页面设置',
name: 'common',
style: {
width: '100vw',
height: '100vh',
borderRadius: '4'
},
data: []
});
const emit = defineEmits<{
(e: 'done', index: number): void;
(e: 'update'): void;
}>();
// 表单数据
const { form, assignFields } = useFormData<Design>({
pageId: undefined,
home: undefined,
layout: undefined
});
// 表单验证规则
const rules = reactive({
name: [
{
required: true,
type: 'string',
message: '请填写页面名称',
trigger: 'blur'
}
],
path: [
{
required: true,
type: 'string',
message: '请填写路由地址',
trigger: 'blur'
}
],
component: [
{
required: true,
type: 'string',
message: '请填写组件路径',
trigger: 'blur'
}
]
});
const openEdit = () => {
emit('update');
};
const updateColor = (color: string) => {
item.value.style.background = color;
emit('done', item);
};
const onClick = () => {
emit('done', Number(props.index));
};
watch(
() => props.data,
(data) => {
console.log(data);
}
);
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,58 @@
<!-- 选择下拉框 -->
<template>
<a-radio-group
v-model:value="value"
: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 updateValue = (value: string) => {
console.log(value);
emit('update:value', value);
};
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
</script>

View File

@@ -0,0 +1,53 @@
<!-- 选择下拉框 -->
<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"
/>
</template>
<script lang="ts" setup>
import { getDictionaryOptions } from '@/utils/common';
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;
allowClear?: boolean;
width?: number;
index?: number;
}>(),
{
placeholder: '请选择服务器厂商'
}
);
// 字典数据
const data = getDictionaryOptions(props.dictCode);
/* 更新选中数据 */
const updateValue = (value: string) => {
console.log(value);
emit('update:value', value);
emit('index', Number(props.index));
};
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
</script>

View 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>

View 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>

View 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>

View 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;
}

View File

@@ -0,0 +1,15 @@
/**
* 行业类型
*/
export interface IndustryData {
label: string;
value: string;
children?: {
value: string;
label: string;
children?: {
value: string;
label: string;
}[];
}[];
}

View 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>

View 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');
}
});

View 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>

View 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;
}

View File

@@ -0,0 +1,15 @@
/**
* 省市区数据类型
*/
export interface RegionsData {
label: string;
value: string;
children?: {
value: string;
label: string;
children?: {
value: string;
label: string;
}[];
}[];
}

View 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>

View File

@@ -0,0 +1,166 @@
<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="appId"
: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.appName"
placeholder="请输入搜索关键词"
style="width: 200px"
@search="reload"
@pressEnter="reload"
/>
</a-space>
</template>
<template #bodyCell="{ column, record }">
<template v-if="column.key === 'appName'">
<div class="app-box">
<a-image
:height="45"
:width="45"
:preview="false"
:src="record.appIcon"
fallback="https://file.wsdns.cn/20230218/550e610d43334dd2a7f66d5b20bd58eb.svg"
/>
<div class="app-info">
<a class="ele-text-heading">
{{ record.appName }}
</a>
<span class="ele-text-placeholder">
{{ record.companyName }}
</span>
</div>
</div>
</template>
<template v-if="column.key === 'action'">
<a-radio @click="onDone(record)" />
<span class="ele-text-secondary">选择</span>
</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 { App, AppParam } from '@/api/oa/app/model';
import { EleProTable } from 'ele-admin-pro';
import useSearch from '@/utils/use-search';
import { pageApp } from '@/api/oa/app';
defineProps<{
// 弹窗是否打开
visible: boolean;
title?: any;
// 修改回显的数据
data?: App | null;
}>();
const emit = defineEmits<{
(e: 'done', data: App): void;
(e: 'update:visible', visible: boolean): void;
}>();
/* 更新visible */
const updateVisible = (value: boolean) => {
emit('update:visible', value);
};
// 表单数据
const { where } = useSearch<AppParam>({
appId: undefined,
appName: undefined,
nickname: '',
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: '名称',
dataIndex: 'appName',
key: 'appName'
},
{
title: '操作',
key: 'action',
align: 'center',
hideInSetting: true
}
]);
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
return pageApp({
...where,
...orders,
page,
limit
});
};
/* 搜索 */
const reload = () => {
// selection.value = [];
tableRef?.value?.reload({ page: 1, where });
};
const onDone = (record: App) => {
updateVisible(false);
emit('done', record);
};
/* 自定义行属性 */
const customRow = (record: App) => {
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>

View File

@@ -0,0 +1,63 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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 { App } from '@/api/oa/app/model';
const props = withDefaults(
defineProps<{
value?: any;
placeholder?: string;
index?: number;
}>(),
{
placeholder: '请选择项目'
}
);
const emit = defineEmits<{
(e: 'done', App): void;
(e: 'clear'): void;
}>();
// 是否显示编辑弹窗
const showEdit = ref(false);
// 当前编辑数据
const current = ref<App | null>(null);
/* 打开编辑弹窗 */
const openEdit = (row?: App) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
row.index = Number(props.index);
emit('done', row);
};
// 查询租户列表
// const appList = ref<App[] | undefined>([]);
</script>

View 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>

View File

@@ -0,0 +1,59 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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 { Design } from '@/api/cms/design/model';
withDefaults(
defineProps<{
value?: any;
placeholder?: string;
}>(),
{
placeholder: '请选择数据'
}
);
const emit = defineEmits<{
(e: 'done', Design): void;
(e: 'clear'): void;
}>();
// 是否显示编辑弹窗
const showEdit = ref(false);
// 当前编辑数据
const current = ref<Design | null>(null);
/* 打开编辑弹窗 */
const openEdit = (row?: Design) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
</script>

View 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>

View File

@@ -0,0 +1,59 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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 { Design } from '@/api/cms/design/model';
withDefaults(
defineProps<{
value?: any;
placeholder?: string;
}>(),
{
placeholder: '请选择数据'
}
);
const emit = defineEmits<{
(e: 'done', Design): void;
(e: 'clear'): void;
}>();
// 是否显示编辑弹窗
const showEdit = ref(false);
// 当前编辑数据
const current = ref<Design | null>(null);
/* 打开编辑弹窗 */
const openEdit = (row?: Design) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
</script>

View File

@@ -0,0 +1,158 @@
<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="companyId"
: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="record.companyLogo"
:preview="false"
:width="45"
/>
</template>
<template v-if="column.key === 'companyType'">
<a-tag v-if="record.companyType === 10">企业</a-tag>
<a-tag v-if="record.companyType === 20">政府单位</a-tag>
</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 { pageCompany } from '@/api/oa/company';
import { EleProTable } from 'ele-admin-pro';
import { Company, CompanyParam } from '@/api/oa/company/model';
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 标题
title?: string;
// 企业类型
companyType?: string;
// 修改回显的数据
data?: Company | 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: '操作',
key: 'action',
align: 'center'
},
{
title: 'LOGO',
dataIndex: 'companyLogo',
key: 'companyLogo',
align: 'center'
},
{
title: '企业名称',
dataIndex: 'companyName'
},
{
title: '企业类型',
dataIndex: 'companyType',
key: 'companyType'
}
]);
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
where = {};
// 搜索条件
if (searchText.value) {
where.keywords = searchText.value;
}
if (props.companyType == 'empty') {
where.emptyType = true;
} else {
where.companyType = props.companyType;
}
where.isStaff = true;
return pageCompany({
...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 {
// 行点击事件
// onClick: () => {
// updateVisible(false);
// emit('done', record);
// },
// 行双击事件
onDblclick: () => {
updateVisible(false);
emit('done', record);
}
};
};
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,61 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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';
withDefaults(
defineProps<{
value?: any;
customerType?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择数据'
}
);
const emit = defineEmits<{
(e: 'done', Customer): void;
(e: 'clear'): void;
}>();
// 是否显示编辑弹窗
const showEdit = ref(false);
// 当前编辑数据
const current = ref<Company | null>(null);
/* 打开编辑弹窗 */
const openEdit = (row?: Company) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
</script>

View 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="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 === 'action'">
<template v-if="pageId == record.pageId">
<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 { pageDesign } from '@/api/cms/design';
import { EleProTable } from 'ele-admin-pro';
import { Design, DesignParam } from '@/api/cms/design/model';
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 标题
title?: string;
// 修改回显的数据
data?: Design | null;
}>();
const emit = defineEmits<{
(e: 'done', data: Design): 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 pageDesign({
...where,
...orders,
page,
limit
});
};
/* 搜索 */
const reload = (where?: DesignParam) => {
tableRef?.value?.reload({ page: 1, where });
};
const onRadio = (record: Design) => {
pageId.value = Number(record.pageId)
updateVisible(false);
emit('done', record);
};
/* 自定义行属性 */
const customRow = (record: Design) => {
return {
// 行点击事件
// onClick: () => {
// updateVisible(false);
// emit('done', record);
// },
// 行双击事件
onDblclick: () => {
updateVisible(false);
emit('done', record);
}
};
};
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,59 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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 { Design } from '@/api/cms/design/model';
withDefaults(
defineProps<{
value?: any;
placeholder?: string;
}>(),
{
placeholder: '请选择数据'
}
);
const emit = defineEmits<{
(e: 'done', Design): void;
(e: 'clear'): void;
}>();
// 是否显示编辑弹窗
const showEdit = ref(false);
// 当前编辑数据
const current = ref<Design | null>(null);
/* 打开编辑弹窗 */
const openEdit = (row?: Design) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
</script>

View File

@@ -0,0 +1,142 @@
<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';
defineProps<{
// 弹窗是否打开
visible: boolean;
title?: any;
// 修改回显的数据
data?: DictData | null;
}>();
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[]>([
{
key: 'index',
width: 48,
align: 'center',
fixed: 'left',
hideInSetting: true,
customRender: ({ index }) => index + (tableRef.value?.tableIndex ?? 0)
},
{
title: '名称',
dataIndex: 'dictDataName',
key: 'dictDataName'
},
{
title: '操作',
key: 'action',
align: 'center',
hideInSetting: true
}
]);
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
where.dictCode = 'groupId';
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>

View File

@@ -0,0 +1,63 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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 { Dict } from '@/api/system/dict/model';
const props = withDefaults(
defineProps<{
value?: any;
placeholder?: string;
index?: number;
}>(),
{
placeholder: '请选择字典'
}
);
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>

View File

@@ -0,0 +1,152 @@
<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="bookId"
: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 === 'photo'">
<a-image :src="record.photo" :width="80" />
</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>
<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 { pageDocsBook } from '@/api/cms/docs-book';
import { EleProTable } from 'ele-admin-pro';
import { DocsBook, DocsBookParam } from '@/api/cms/docs-book/model';
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 标题
title?: string;
// 修改回显的数据
data?: DocsBook | null;
}>();
const emit = defineEmits<{
(e: 'done', data: DocsBook): 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: 'ID',
dataIndex: 'bookId',
key: 'bookId'
},
{
title: '封面图',
dataIndex: 'photo',
key: 'photo'
},
{
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 pageDocsBook({
...where,
...orders,
page,
limit
});
};
/* 搜索 */
const reload = (where?: DocsBookParam) => {
tableRef?.value?.reload({ page: 1, where });
};
const onRadio = (record: DocsBook) => {
pageId.value = Number(record.bookId);
updateVisible(false);
emit('done', record);
};
/* 自定义行属性 */
const customRow = (record: DocsBook) => {
return {
// 行点击事件
// onClick: () => {
// updateVisible(false);
// emit('done', record);
// },
// 行双击事件
onDblclick: () => {
updateVisible(false);
emit('done', record);
}
};
};
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,59 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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 { DocsBook } from '@/api/cms/docs-book/model';
withDefaults(
defineProps<{
value?: any;
placeholder?: string;
}>(),
{
placeholder: '请选择数据'
}
);
const emit = defineEmits<{
(e: 'done', DocsBook): void;
(e: 'clear'): void;
}>();
// 是否显示编辑弹窗
const showEdit = ref(false);
// 当前编辑数据
const current = ref<DocsBook | null>(null);
/* 打开编辑弹窗 */
const openEdit = (row?: DocsBook) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
</script>

View 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>

View File

@@ -0,0 +1,339 @@
<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"
: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-space>
</div>
<a-button
style="margin-right: 20px"
@click="openUrl('/cms/photo/dict')"
>管理分组</a-button
>
</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>
<lebal>
<a-radio @click="onRadio(record)" />
<a class="ele-text-success">选择</a>
</lebal>
<a-divider type="vertical" />
<a class="ele-text-placeholder">编辑</a>
<a-divider type="vertical" />
<a class="ele-text-placeholder">删除</a>
</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, 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, openNew, 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;
}
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 customRow = (record: FileRecord) => {
return {
// 行点击事件
// onClick: () => {
// updateVisible(false);
// emit('done', record);
// },
// 行双击事件
onDblclick: () => {
updateVisible(false);
emit('done', record);
}
};
};
watch(
() => props.visible,
(visible) => {
if (visible) {
getGroupList();
}
}
);
</script>

View File

@@ -0,0 +1,131 @@
<template>
<a-image-preview-group>
<a-space>
<template v-for="(item, index) in data" :key="index">
<div class="image-upload-item" v-if="type == 'video'">
{{ item.url }}
<a class="image-upload-close" @click="onDeleteItem(index)">
<CloseOutlined />
</a>
</div>
<div class="image-upload-item" v-else>
<a-image
:width="width"
:height="width"
style="border: 1px dashed var(--grey-7)"
:src="item.url"
/>
<a class="image-upload-close" @click="onDeleteItem(index)">
<CloseOutlined />
</a>
</div>
</template>
<a-button
@click="openEdit"
v-if="data?.length < limit"
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 } from '@ant-design/icons-vue';
import { ref } from 'vue';
import SelectData from './components/select-data.vue';
import { FileRecord } from '@/api/system/file/model';
const props = withDefaults(
defineProps<{
value?: any;
data?: any[];
width?: number;
type?: string;
limit?: number;
placeholder?: string;
index?: number;
}>(),
{
placeholder: '请选择数据',
width: 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);
width: 80px;
height: 80px;
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>

View 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>

View File

@@ -0,0 +1,59 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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';
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 openEdit = (row?: Form) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
</script>

View 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"
@change="onChange"
/>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import type { ValueType } from 'ant-design-vue/es/vc-cascader/Cascader';
import { listGoodsCategory } from '@/api/shop/goodsCategory';
import { toTreeData } from 'ele-admin-pro/es';
import { GoodsCategory } from '@/api/shop/goodsCategory/model';
const props = withDefaults(
defineProps<{
value?: string[];
placeholder?: string;
options?: GoodsCategory[];
valueField?: 'label';
type?: 'provinceCity' | 'province';
showSearch?: boolean;
}>(),
{
showSearch: true
}
);
const emit = defineEmits<{
(e: 'done', item?: any, value?: ValueType);
(e: 'update:value', value?: string[]): void;
(e: 'load-data-done', value: GoodsCategory[]): void;
}>();
// 级联选择器数据
const regionsData = ref<GoodsCategory[]>([]);
/* 更新 value */
const updateValue = (value: ValueType) => {
emit('update:value', value as string[]);
};
const onChange = (item: any, value: ValueType) => {
emit('done', item, value);
};
/* 级联选择器数据 value 处理 */
const formatData = (data: GoodsCategory[]) => {
if (props.valueField === 'label') {
return data.map((d) => {
const item: GoodsCategory = {
label: d.title,
value: d.categoryId
};
if (d.children) {
item.children = d.children.map((c) => {
const cItem: GoodsCategory = {
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: GoodsCategory[]) => {
return formatData(
data.map((d) => {
const item: GoodsCategory = {
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,
() => {
listGoodsCategory().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>

View 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;
}

View File

@@ -0,0 +1,15 @@
/**
* 行业类型
*/
export interface IndustryData {
label: string;
value: string;
children?: {
value: string;
label: string;
children?: {
value: string;
label: string;
}[];
}[];
}

View 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>

View File

@@ -0,0 +1,59 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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';
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 openEdit = (row?: Grade) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
</script>

View 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>

View File

@@ -0,0 +1,59 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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';
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 openEdit = (row?: Group) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
</script>

View File

@@ -0,0 +1,157 @@
<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-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 { pageMerchant } from '@/api/shop/merchant';
import { EleProTable } from 'ele-admin-pro';
import { Merchant, MerchantParam } from '@/api/shop/merchant/model';
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 标题
title?: string;
// 商户类型
shopType?: string;
// 修改回显的数据
data?: Merchant | null;
}>();
const emit = defineEmits<{
(e: 'done', data: Merchant): 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: 'LOGO',
dataIndex: 'image',
key: 'image',
align: 'center'
},
{
title: '商户名称',
dataIndex: 'merchantName'
},
{
title: '商户类型',
dataIndex: 'shopType',
key: 'shopType'
}
]);
// 表格数据源
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 pageMerchant({
...where,
...orders,
page,
limit
});
};
/* 搜索 */
const reload = (where?: MerchantParam) => {
tableRef?.value?.reload({ page: 1, where });
};
const onRadio = (record: Merchant) => {
updateVisible(false);
emit('done', record);
};
/* 自定义行属性 */
const customRow = (record: Merchant) => {
return {
// 行点击事件
// onClick: () => {
// updateVisible(false);
// emit('done', record);
// },
// 行双击事件
onDblclick: () => {
updateVisible(false);
emit('done', record);
}
};
};
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,61 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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 { Merchant } from '@/api/shop/merchant/model';
withDefaults(
defineProps<{
value?: any;
customerType?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择商户'
}
);
const emit = defineEmits<{
(e: 'done', Merchant): void;
(e: 'clear'): void;
}>();
// 是否显示编辑弹窗
const showEdit = ref(false);
// 当前编辑数据
const current = ref<Merchant | null>(null);
/* 打开编辑弹窗 */
const openEdit = (row?: Merchant) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
</script>

View File

@@ -0,0 +1,72 @@
<!-- 选择下拉框 -->
<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 { Spec } from '@/api/shop/spec/model';
import { ref } from 'vue';
import { MerchantType } from '@/api/shop/merchantType/model';
import {listMerchantType} from "@/api/shop/merchantType";
const props = withDefaults(
defineProps<{
value?: string;
placeholder?: string;
showSearch?: string;
allowClear?: boolean;
width?: number;
specDict?: Spec[];
index?: number;
}>(),
{
placeholder: '请选择服务器厂商'
}
);
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
(e: 'done', value: Spec, index: number): void;
}>();
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
const data = ref<MerchantType[]>([]);
listMerchantType({}).then(list => {
data.value = list.map(d => {
return {
name: d.name,
value: d.name
}
});
})
const onChange = (value: string) => {
props.specDict?.map((d) => {
if (d.value == value) {
emit('done', d, Number(props.index));
}
});
};
</script>

View 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>

View File

@@ -0,0 +1,61 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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';
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 openEdit = (row?: Environment) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
// 查询租户列表
// const appList = ref<App[] | undefined>([]);
</script>

View 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>

View File

@@ -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>

View File

@@ -0,0 +1,59 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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';
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 openEdit = (row?: Organization) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
</script>

View File

@@ -0,0 +1,150 @@
<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'))
}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>

View File

@@ -0,0 +1,62 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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';
import { Role } from '@/api/system/role/model';
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 openEdit = (row?: Grade) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
</script>

View File

@@ -0,0 +1,58 @@
<!-- 选择下拉框 -->
<template>
<a-select
:allow-clear="allowClear"
:show-search="showSearch"
optionFilterProp="label"
:options="specDict"
:value="value"
:placeholder="placeholder"
@update:value="updateValue"
:style="`width: ${width}px`"
@blur="onBlur"
@change="onChange"
/>
</template>
<script lang="ts" setup>
import { Spec } from '@/api/shop/spec/model';
const props = withDefaults(
defineProps<{
value?: string;
placeholder?: string;
showSearch?: string;
allowClear?: boolean;
width?: number;
specDict?: Spec[];
index?: number;
}>(),
{
placeholder: '请选择服务器厂商'
}
);
const emit = defineEmits<{
(e: 'update:value', value: string): void;
(e: 'blur'): void;
(e: 'done', value: Spec, index: number): void;
}>();
/* 更新选中数据 */
const updateValue = (value: string) => {
emit('update:value', value);
};
/* 失去焦点 */
const onBlur = () => {
emit('blur');
};
const onChange = (value: string) => {
props.specDict?.map((d) => {
if (d.value == value) {
emit('done', d, Number(props.index));
}
});
};
</script>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,65 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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 openEdit = (row?: User) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
row.index = Number(props.index);
emit('done', row);
};
// 查询租户列表
// const appList = ref<App[] | undefined>([]);
</script>

View 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="companyId"
: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 === 'companyType'">
<a-tag v-if="record.companyType === 10">企业</a-tag>
<a-tag v-if="record.companyType === 20">政府单位</a-tag>
</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 { pageTenant } from '@/api/system/tennat';
import { FILE_THUMBNAIL } from '@/config/setting';
import { EleProTable } from 'ele-admin-pro';
import { Company, CompanyParam } from '@/api/system/company/model';
const props = defineProps<{
// 弹窗是否打开
visible: boolean;
// 标题
title?: string;
// 企业类型
companyType?: string;
// 修改回显的数据
data?: Company | 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: 'tenantName'
},
{
title: '描述',
dataIndex: 'comments',
key: 'comments'
},
{
title: '操作',
key: 'action',
align: 'center'
}
]);
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
where = {};
// 搜索条件
if (searchText.value) {
where.keywords = searchText.value;
}
return pageTenant({
...where,
...orders,
page,
limit
});
};
/* 搜索 */
const reload = (where?: CompanyParam) => {
tableRef?.value?.reload({ page: 1, where });
};
/* 自定义行属性 */
const customRow = (record: Company) => {
return {
// 行点击事件
onClick: () => {
updateVisible(false);
emit('done', record);
}
};
};
</script>
<style lang="less"></style>

View File

@@ -0,0 +1,61 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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 { Tenant } from '@/api/system/tennat/model';
withDefaults(
defineProps<{
value?: any;
customerType?: string;
placeholder?: string;
}>(),
{
placeholder: '请选择租户模版'
}
);
const emit = defineEmits<{
(e: 'done', Customer): void;
(e: 'clear'): void;
}>();
// 是否显示编辑弹窗
const showEdit = ref(false);
// 当前编辑数据
const current = ref<Tenant | null>(null);
/* 打开编辑弹窗 */
const openEdit = (row?: Tenant) => {
current.value = row ?? null;
showEdit.value = true;
};
const onChange = (row) => {
emit('done', row);
};
</script>

View 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="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';
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 { 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: 'nickname'
},
{
title: '联系电话',
dataIndex: 'phone',
key: 'phone'
},
{
title: '所属部门',
dataIndex: 'organizationName'
},
{
title: '操作',
key: 'action',
align: 'center',
hideInSetting: true
}
]);
// 表格数据源
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
// 搜索条件
if (searchText.value) {
where.keywords = searchText.value;
}
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>

View 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>

View File

@@ -0,0 +1,61 @@
<template>
<div>
<a-input-group compact>
<a-input
disabled
style="width: calc(100% - 32px)"
v-model:value="value"
: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 { 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) => {
emit('done', row);
};
// 查询租户列表
// const appList = ref<App[] | undefined>([]);
</script>

View File

@@ -0,0 +1,310 @@
<template>
<div class="phone-layout" v-if="form">
<div class="phone-header-black ele-fluid">
<div class="title ele-fluid"> 商品详情 </div>
</div>
<template v-if="form.title">
<div class="phone-body" style="overflow-y: auto; overflow-x: hidden">
<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.fields">
<template
v-for="(img, index) in JSON.parse(form.fields)"
:key="index"
>
<div class="ad-item">
<a-image :preview="false" :src="img" width="100%" />
</div>
</template>
</template>
<template v-else>
<div class="ad-item">
<a-image :preview="false" :src="form.image" width="100%" />
</div>
</template>
</a-carousel>
<a-card
v-if="form.title"
class="goods"
:bordered="false"
:body-style="{ padding: '16px' }"
>
<div class="ele-cell price ele-text-danger">
<div class="ele-cell-content ele-cell">
<span class="ele-text-danger"><sub></sub></span>
<a-statistic
:precision="2"
:value="`${form.price ? form.price : 0}`"
:valueStyle="{ color: 'var(--error-color)' }"
/>
</div>
<div class="ele-cell-desc">已售 {{ formatNumber(form.sales) }}</div>
</div>
<div class="ele-cell-title">{{ form.title }}</div>
<div class="ele-cell goods-attr">
<div class="ele-cell-content">
<a-tag color="orange">交通银行满减</a-tag>
<a-tag color="orange">满30减10</a-tag>
</div>
</div>
</a-card>
<a-divider class="goods-divider" />
<a-card
class="goods"
:bordered="false"
:body-style="{ padding: '8px 16px' }"
>
<div class="ele-cell goods-attr">
<div class="ele-text-secondary">已选择</div>
<div class="ele-cell-content">颜色</div>
<div class="ele-cell-desc"><RightOutlined /></div>
</div>
</a-card>
<a-divider class="goods-divider" />
<a-card
class="goods"
:bordered="false"
:body-style="{ padding: '8px 16px' }"
>
<div class="ele-cell goods-attr">
<div class="ele-text-secondary">用户评价(0)</div>
<div class="ele-cell-content"></div>
</div>
</a-card>
<a-divider style="padding: 0 30px">商品详情</a-divider>
<a-card
class="goods"
:bordered="false"
:body-style="{ padding: '16px' }"
>
<!-- 文档内容 -->
<tinymce-editor
id="content"
v-model:value="form.content"
:disabled="true"
:init="config"
/>
</a-card>
<div class="goods-content" v-if="form.files">
<template v-for="(img, index) in JSON.parse(form.files)" :key="index">
<div class="ad-item">
<a-image :preview="false" :src="img" width="100%" />
</div>
</template>
</div>
<a-card
class="buy-bar"
:bordered="false"
:body-style="{ padding: '12px 16px' }"
>
<div class="ele-cell">
<div class="shop-btn ele-text-secondary">
<ShopOutlined class="icon ele-text-danger" />
店铺
</div>
<div class="kefu-btn ele-text-secondary">
<CustomerServiceOutlined class="icon ele-text-warning" />
客服
</div>
<div class="star-btn ele-text-secondary">
<StarOutlined class="icon" />
收藏
</div>
<div class="ele-cell-content buy-btn">
<a-space :size="0">
<a-button class="add-cart">加入购物车</a-button
><a-button class="buy-now">立即购买</a-button>
</a-space>
</div>
</div>
</a-card>
</div>
</template>
</div>
</template>
<script lang="ts" setup>
import {
LeftCircleOutlined,
RightCircleOutlined,
RightOutlined,
ShopOutlined,
CustomerServiceOutlined,
StarOutlined
} from '@ant-design/icons-vue';
import { formatNumber } from 'ele-admin-pro/es';
import { ref } from 'vue';
withDefaults(
defineProps<{
value?: string;
placeholder?: string;
form?: any | null;
}>(),
{
placeholder: undefined
}
);
const config = ref({
selector: '#content', //容器可使用css选择器
branding: false,
language: 'zh_CN', //调用放在langs文件夹内的语言包
toolbar: false, //隐藏工具栏
menubar: false, //隐藏菜单栏
inline: true, //开启内联模式
plugins: [] //选择需加载的插件
//选中时出现的快捷工具,与插件有依赖关系
// quickbars_selection_toolbar: 'bold italic forecolor | link blockquote quickimage',
// init_instance_callback: function (editor) {
// editor.setContent('这里是你的内容字符串');
// }
});
</script>
<style lang="less" scoped>
.phone-layout {
position: fixed;
right: 16px;
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;
}
}
.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;
}
.shop-btn,
.kefu-btn,
.star-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;
}
</style>

View 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>

View File

@@ -0,0 +1,242 @@
<!-- 富文本编辑器 -->
<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
} 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;
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>

View File

@@ -0,0 +1,248 @@
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',
'|',
'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: () => {}
};
// 暗黑主题配置
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;
}

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -0,0 +1,9 @@
/**
* 搜索表单类型
*/
export interface WhereType {
keywords?: string;
nickname?: string;
userId?: number;
phone?: string;
}

View 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>

View File

@@ -0,0 +1,9 @@
/**
* 搜索表单类型
*/
export interface WhereType {
keywords?: string;
nickname?: string;
userId?: number;
phone?: string;
}

View 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>