Initial commit
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
<!-- 类型选择 -->
|
||||
<template>
|
||||
<a-radio-group
|
||||
:value="value"
|
||||
@blur="onBlur"
|
||||
@update:value="updateValue"
|
||||
:options="data"
|
||||
/>
|
||||
</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>
|
||||
@@ -0,0 +1,46 @@
|
||||
<!-- 角色选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
allow-clear
|
||||
:value="value"
|
||||
: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;
|
||||
placeholder?: string;
|
||||
dictCode?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择服务器厂商'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = getDictionaryOptions(props.dictCode);
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,268 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
width="680px"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '编辑链接' : '添加链接'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 4 }, sm: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 18 }, sm: { span: 24 } }"
|
||||
layout="horizontal"
|
||||
>
|
||||
<a-form-item label="类型" v-bind="validateInfos.type">
|
||||
<dict-radio
|
||||
v-model:value="form.type"
|
||||
:dictCode="'linkType'"
|
||||
:placeholder="'请选择链接类型'"
|
||||
@change="onChange"
|
||||
@blur="validate('type', { trigger: 'blur' }).catch(() => {})"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="链接名称" v-bind="validateInfos.linkName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入链接名称"
|
||||
v-model:value="form.linkName"
|
||||
@blur="validate('linkName', { trigger: 'blur' }).catch(() => {})"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="描述" v-bind="validateInfos.comments">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入备注"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="form.type === '0'"
|
||||
label="组件路径"
|
||||
v-bind="validateInfos.linkComponent"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入组件路径"
|
||||
v-model:value="form.linkComponent"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="form.type === '1'"
|
||||
label="链接地址"
|
||||
v-bind="validateInfos.linkUrl"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入官方网址"
|
||||
v-model:value="form.linkUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="图标路径" v-bind="validateInfos.linkIcon">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请填写图标路径"
|
||||
v-model:value="form.linkIcon"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="链接分类"
|
||||
v-bind="validateInfos.category"
|
||||
v-if="form.type === '1'"
|
||||
>
|
||||
<dict-select
|
||||
v-model:value="form.category"
|
||||
:dictCode="'linkCategory'"
|
||||
:placeholder="'请选择链接类别'"
|
||||
@blur="validate('category', { trigger: 'blur' }).catch(() => {})"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="下载地址"
|
||||
v-bind="validateInfos.linkDown"
|
||||
v-if="form.type === '1'"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入下载地址"
|
||||
v-model:value="form.linkDown"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="访问账号"
|
||||
v-bind="validateInfos.linkAccount"
|
||||
v-if="form.type === '1'"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请填写访问账号"
|
||||
v-model:value="form.linkAccount"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="访问密码"
|
||||
v-bind="validateInfos.linkPassword"
|
||||
v-if="form.type === '1'"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请填写访问密码"
|
||||
v-model:value="form.linkPassword"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" v-bind="validateInfos.sortNumber">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="排序"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import DictSelect from './dict-select.vue';
|
||||
import DictRadio from './dict-radio.vue';
|
||||
import { addLink, updateLink } from '@/api/apps/link';
|
||||
import type { Link } from '@/api/apps/link/model';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
// 是否显示
|
||||
const isShow = ref(false);
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Link | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 用户信息
|
||||
const form = reactive<Link>({
|
||||
linkId: 0,
|
||||
linkName: '',
|
||||
linkUrl: '',
|
||||
linkAccount: '',
|
||||
linkPassword: '',
|
||||
linkDown: '',
|
||||
linkIcon: '',
|
||||
type: '0',
|
||||
category: '',
|
||||
clicks: undefined,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
linkName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入链接名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
comments: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入功能描述',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
type: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请选项链接类型',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
linkUrl: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写组件路径或链接地址',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* openType选择改变 */
|
||||
const onChange = () => {
|
||||
isShow.value = !isShow.value;
|
||||
};
|
||||
|
||||
const { resetFields, validate, validateInfos } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const data = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateLink : addLink;
|
||||
saveOrUpdate(data)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
loading.value = false;
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
isShow.value = props.data.type === '0';
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="less">
|
||||
.tab-pane {
|
||||
min-height: 300px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:lg="6"
|
||||
:md="8"
|
||||
:sm="12"
|
||||
:xs="24"
|
||||
>
|
||||
<a-badge-ribbon :text="item.category">
|
||||
<a-card :bordered="false" hoverable style="margin-top: 16px">
|
||||
<div class="ele-cell" style="margin-bottom: 16px">
|
||||
<a-avatar :src="item.linkIcon" :size="50" />
|
||||
<div class="ele-cell-content">
|
||||
<h4 class="link-name">{{ item.linkName }}</h4>
|
||||
<small class="ele-text-placeholder" v-if="item.type === '1'">
|
||||
{{ item.linkUrl }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ele-elip" style="margin-bottom: 6px">
|
||||
{{ item.comments }}
|
||||
</div>
|
||||
<div class="ele-elip ele-text-placeholder">{{
|
||||
item.createTime
|
||||
}}</div>
|
||||
<template #actions>
|
||||
<a-tooltip title="访问链接">
|
||||
<chrome-outlined @click="openUrl(item)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip title="复制链接">
|
||||
<share-alt-outlined @click="copyUrl(item)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="item.linkDown">
|
||||
<download-outlined @click="downUrl(item)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip title="编辑">
|
||||
<edit-outlined @click="openEdit(item)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-card>
|
||||
</a-badge-ribbon>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="ele-text-center" style="margin-top: 18px">
|
||||
<a-pagination
|
||||
:total="where.count"
|
||||
v-model:current="where.page"
|
||||
v-model:page-size="where.limit"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!list.length" style="margin-top: 46px">
|
||||
<a-empty />
|
||||
</div>
|
||||
<!-- 编辑弹窗 -->
|
||||
<link-edit v-model:visible="showEdit" :data="current" @done="onSearch" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { pageLink } from '@/api/apps/link';
|
||||
import type { Link, LinkParam } from '@/api/apps/link/model';
|
||||
import LinkEdit from './components/link-edit.vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import {
|
||||
ChromeOutlined,
|
||||
EyeOutlined,
|
||||
DownloadOutlined,
|
||||
EditOutlined,
|
||||
ShareAltOutlined,
|
||||
EllipsisOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import router from '@/router';
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<Link | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
const linkType = getDictionaryOptions('linkType');
|
||||
|
||||
// 打开链接
|
||||
const openUrl = (item) => {
|
||||
if (item.type == '0') {
|
||||
return router.push(item.linkComponent);
|
||||
}
|
||||
return window.open(item.linkUrl);
|
||||
};
|
||||
|
||||
// 下载文件
|
||||
const downUrl = (url) => {
|
||||
if (url.linkDown) {
|
||||
return window.open(url.linkDown);
|
||||
}
|
||||
};
|
||||
|
||||
// 复制文本
|
||||
const copyUrl = (item) => {
|
||||
// 模拟 输入框
|
||||
const cInput = document.createElement('input');
|
||||
cInput.value = item.linkDown ? item.linkDown : item.linkUrl;
|
||||
document.body.appendChild(cInput);
|
||||
cInput.select(); // 选取文本框内容
|
||||
|
||||
// 执行浏览器复制命令
|
||||
// 复制命令会将当前选中的内容复制到剪切板中(这里就是创建的input标签)
|
||||
// Input要在正常的编辑状态下原生复制方法才会生效
|
||||
message.success('复制成功');
|
||||
document.execCommand('copy');
|
||||
|
||||
// 复制成功后再将构造的标签 移除
|
||||
document.body.removeChild(cInput);
|
||||
};
|
||||
|
||||
const list = ref<Link[]>([]);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Link) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
// 表单数据
|
||||
const { where } = useSearch<LinkParam>({
|
||||
page: 1,
|
||||
limit: 36,
|
||||
count: 0,
|
||||
linkName: '',
|
||||
list: []
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const onSearch = (linkName: string) => {
|
||||
// 重置当前页面
|
||||
if (linkName) {
|
||||
where.page = 1;
|
||||
}
|
||||
pageLink({ ...where, linkName }).then((data) => {
|
||||
where.count = data?.count;
|
||||
list.value = data.list;
|
||||
});
|
||||
};
|
||||
|
||||
// 翻页
|
||||
const onChange = (e) => {
|
||||
where.page = e;
|
||||
onSearch();
|
||||
};
|
||||
|
||||
onSearch();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Links'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.app-btn-search {
|
||||
margin-left: 12px;
|
||||
height: 40px;
|
||||
}
|
||||
.link-name {
|
||||
line-height: 1;
|
||||
}
|
||||
.a-link {
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,254 @@
|
||||
<template>
|
||||
<div :class="['ele-body', { 'list-article-responsive': styleResponsive }]">
|
||||
<a-card>
|
||||
<a-image-preview-group>
|
||||
<a-list
|
||||
:data-source="data"
|
||||
:loading="loading && page === 1"
|
||||
item-layout="vertical"
|
||||
size="large"
|
||||
>
|
||||
<template #renderItem="{ item, index }">
|
||||
<a-list-item :key="index">
|
||||
<a-list-item-meta :title="item.title">
|
||||
<template #description>
|
||||
<a-tag v-for="(tag, i) in item.tags" :key="i">
|
||||
{{ tag }}
|
||||
</a-tag>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
<div class="ele-text-heading">
|
||||
{{ item.content }}
|
||||
</div>
|
||||
<div class="ele-cell" style="margin-top: 16px">
|
||||
<a-avatar :src="item.avatar" size="small" />
|
||||
<div class="ele-cell-content">
|
||||
{{ item.user }} 发表于 {{ item.time }}
|
||||
</div>
|
||||
</div>
|
||||
<template #extra>
|
||||
<div class="list-image-wrap">
|
||||
<a-image width="100%" :src="item.cover" />
|
||||
</div>
|
||||
</template>
|
||||
<template #actions>
|
||||
<span>
|
||||
<like-outlined />
|
||||
<span><s></s>{{ item.likes }}</span>
|
||||
</span>
|
||||
<span>
|
||||
<star-outlined />
|
||||
<span><s></s>{{ item.favorites }}</span>
|
||||
</span>
|
||||
<span>
|
||||
<message-outlined />
|
||||
<span><s></s>{{ item.comments }}</span>
|
||||
</span>
|
||||
</template>
|
||||
</a-list-item>
|
||||
</template>
|
||||
<template #loadMore>
|
||||
<div class="ele-text-center" style="margin-top: 16px">
|
||||
<a-button v-if="page !== 1" :loading="loading" @click="query">
|
||||
{{ loading ? '加载中..' : '加载更多' }}
|
||||
</a-button>
|
||||
</div>
|
||||
</template>
|
||||
</a-list>
|
||||
</a-image-preview-group>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
LikeOutlined,
|
||||
StarOutlined,
|
||||
MessageOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
|
||||
interface DataType {
|
||||
title: string;
|
||||
content: string;
|
||||
time: string;
|
||||
cover: string;
|
||||
tags: string[];
|
||||
user: string;
|
||||
avatar: string;
|
||||
favorites: number;
|
||||
likes: number;
|
||||
comments: number;
|
||||
}
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const data = ref<DataType[]>([]);
|
||||
|
||||
const loading = ref(false);
|
||||
|
||||
const page = ref(2);
|
||||
|
||||
const query = () => {
|
||||
loading.value = true;
|
||||
setTimeout(() => {
|
||||
loading.value = false;
|
||||
page.value++;
|
||||
data.value = data.value.concat(data.value.slice(0, 3));
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
data.value = [
|
||||
{
|
||||
title: 'ElementUI',
|
||||
content:
|
||||
'Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的组件库,提供了配套设计资源,帮助你的网站快速成型。',
|
||||
time: '2 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/RZ8FQmZfHkcffMlTBCJllBFjEhEsObVo.jpg',
|
||||
tags: ['EleAdminPro', 'UI框架', '设计语言'],
|
||||
user: 'SunSmile',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg',
|
||||
favorites: 104,
|
||||
likes: 189,
|
||||
comments: 15
|
||||
},
|
||||
{
|
||||
title: 'Vue.js',
|
||||
content:
|
||||
'Vue 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。',
|
||||
time: '4 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/WLXm7gp1EbLDtvVQgkeQeyq5OtDm00Jd.jpg',
|
||||
tags: ['EleAdminPro', 'UI框架', '设计语言'],
|
||||
user: '你的名字很好听',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/b6a811873e704db49db994053a5019b2.jpg',
|
||||
favorites: 104,
|
||||
likes: 189,
|
||||
comments: 15
|
||||
},
|
||||
{
|
||||
title: 'Vuex',
|
||||
content:
|
||||
'Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。',
|
||||
time: '12 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/4Z0QR2L0J1XStxBh99jVJ8qLfsGsOgjU.jpg',
|
||||
tags: ['EleAdminPro', 'UI框架', '设计语言'],
|
||||
user: '全村人的希望',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg',
|
||||
favorites: 104,
|
||||
likes: 189,
|
||||
comments: 15
|
||||
},
|
||||
{
|
||||
title: 'Vue Router',
|
||||
content:
|
||||
'Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。',
|
||||
time: '14 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/ttkIjNPlVDuv4lUTvRX8GIlM2QqSe0jg.jpg',
|
||||
tags: ['EleAdminPro', 'UI框架', '设计语言'],
|
||||
user: 'Jasmine',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg',
|
||||
favorites: 104,
|
||||
likes: 189,
|
||||
comments: 15
|
||||
},
|
||||
{
|
||||
title: 'Sass',
|
||||
content: 'Sass 是世界上最成熟、稳定、强大的专业级 CSS 扩展语言。',
|
||||
time: '10 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/fAenQ8nvRjL7x0i0jEfuDBZHvJfHf3v6.jpg',
|
||||
tags: ['EleAdminPro', 'UI框架', '设计语言'],
|
||||
user: '酷酷的大叔',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg',
|
||||
favorites: 104,
|
||||
likes: 189,
|
||||
comments: 15
|
||||
},
|
||||
{
|
||||
title: 'Axios',
|
||||
content:
|
||||
'Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。',
|
||||
time: '16 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/LrCTN2j94lo9N7wEql7cBr1Ux4rHMvmZ.jpg',
|
||||
tags: ['EleAdminPro', 'UI框架', '设计语言'],
|
||||
user: 'SunSmile',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg',
|
||||
favorites: 104,
|
||||
likes: 189,
|
||||
comments: 15
|
||||
},
|
||||
{
|
||||
title: 'Webpack',
|
||||
content:
|
||||
'webpack 是一个模块打包器。webpack 的主要目标是将 JavaScript 文件打包在一起,打包后的文件用于在浏览器中使用。',
|
||||
time: '6 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/yeKvhT20lMU0f1T3Y743UlGEOLLnZSnp.jpg',
|
||||
tags: ['EleAdminPro', 'UI框架', '设计语言'],
|
||||
user: '全村人的希望',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg',
|
||||
favorites: 104,
|
||||
likes: 189,
|
||||
comments: 15
|
||||
},
|
||||
{
|
||||
title: 'Node.js',
|
||||
content:
|
||||
'Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。',
|
||||
time: '8 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/CyrCNmTJfv7D6GFAg39bjT3eRkkRm5dI.jpg',
|
||||
tags: ['EleAdminPro', 'UI框架', '设计语言'],
|
||||
user: 'Jasmine',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg',
|
||||
favorites: 104,
|
||||
likes: 189,
|
||||
comments: 15
|
||||
}
|
||||
];
|
||||
|
||||
// const keyword = ref('');
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ListCardArticle'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.list-image-wrap {
|
||||
width: 280px;
|
||||
border-radius: 6px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 880px) {
|
||||
.list-article-responsive .list-image-wrap {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 576px) {
|
||||
.list-article-responsive .list-image-wrap {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,60 @@
|
||||
<!-- 类型选择 -->
|
||||
<template>
|
||||
<a-radio-group
|
||||
:value="value"
|
||||
@blur="onBlur"
|
||||
@update:value="updateValue"
|
||||
:options="data"
|
||||
/>
|
||||
</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>
|
||||
@@ -0,0 +1,66 @@
|
||||
<!-- 角色选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
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;
|
||||
multiple?: 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>
|
||||
@@ -0,0 +1,183 @@
|
||||
<!-- 文档编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="620"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '修改文档' : '添加文档'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 7 }, sm: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 17 }, sm: { span: 24 } }"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col :md="12" :sm="24" :xs="24">
|
||||
<a-form-item label="上级目录" v-bind="validateInfos.parentId">
|
||||
<docs-select
|
||||
:data="docs"
|
||||
placeholder="请选择上级目录"
|
||||
v-model:value="form.parentId"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="文档标题" v-bind="validateInfos.title">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入文档标题"
|
||||
v-model:value="form.title"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序号" v-bind="validateInfos.sortNumber">
|
||||
<a-input-number
|
||||
:min="0"
|
||||
:max="99999"
|
||||
class="ele-fluid"
|
||||
placeholder="请输入排序号"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :md="12" :sm="24" :xs="24">
|
||||
<a-form-item label="备注">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入备注"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<div style="margin-bottom: 22px">
|
||||
<a-divider />
|
||||
</div>
|
||||
</a-row>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import DocsSelect from './docs-select.vue';
|
||||
import { addDocs, updateDocs } from '@/api/cms/docs';
|
||||
import type { Docs } from '@/api/cms/docs/model';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Docs | null;
|
||||
// 文档id
|
||||
docsId?: number;
|
||||
// 全部文档
|
||||
docs: Docs[];
|
||||
}>();
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const form = reactive<Docs>({
|
||||
// 文档id
|
||||
docsId: 0,
|
||||
// 文档标题
|
||||
title: '',
|
||||
// 上级文档
|
||||
parentId: 0,
|
||||
// 封面图
|
||||
avatar: '',
|
||||
// 用户ID
|
||||
userId: '',
|
||||
// 所属门店ID
|
||||
shopId: '',
|
||||
// 排序
|
||||
sortNumber: 100,
|
||||
// 备注
|
||||
comments: '',
|
||||
// 内容
|
||||
content: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
title: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入文档标题',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
sortNumber: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入排序号',
|
||||
type: 'number',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
const { resetFields, validate, validateInfos } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const docsData = {
|
||||
...form,
|
||||
parentId: form.parentId || 0
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateDocs : addDocs;
|
||||
saveOrUpdate(docsData)
|
||||
.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) {
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
form.parentId = props.docsId;
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,39 @@
|
||||
<!-- 目录选择下拉框 -->
|
||||
<template>
|
||||
<a-tree-select
|
||||
allow-clear
|
||||
tree-default-expand-all
|
||||
:placeholder="placeholder"
|
||||
:value="value || undefined"
|
||||
:tree-data="data"
|
||||
:dropdown-style="{ maxHeight: '360px', overflow: 'auto' }"
|
||||
@update:value="updateValue"
|
||||
/>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import type { Docs } from '@/api/cms/docs/model';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'update:value', value?: number): void;
|
||||
}>();
|
||||
|
||||
withDefaults(
|
||||
defineProps<{
|
||||
// 选中的数据(v-modal)
|
||||
value?: number;
|
||||
// 提示信息
|
||||
placeholder?: string;
|
||||
// 目录数据
|
||||
data: Docs[];
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择目录'
|
||||
}
|
||||
);
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value?: number) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,67 @@
|
||||
<template>
|
||||
<div class="page">
|
||||
<a-card :bordered="false">
|
||||
<!-- 编辑器 -->
|
||||
<byte-md-viewer :value="content" :plugins="plugins" />
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import 'bytemd/dist/index.min.css';
|
||||
import { computed, ref, watch } from 'vue';
|
||||
import 'github-markdown-css/github-markdown-light.css';
|
||||
import type { Docs } from '@/api/cms/docs/model';
|
||||
import ByteMdViewer from '@/components/ByteMdViewer/index.vue';
|
||||
// import { openUrl } from '@/utils/common';
|
||||
// 链接、删除线、复选框、表格等的插件
|
||||
import gfm from '@bytemd/plugin-gfm';
|
||||
import highlight from '@bytemd/plugin-highlight';
|
||||
// 内容样式,这里用的 github 的 markdown 主题
|
||||
import 'github-markdown-css/github-markdown-light.css';
|
||||
// 引入高亮css
|
||||
import 'highlight.js/styles/vs.css';
|
||||
// import { getDocsContent } from '@/api/cms/docs-content';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const props = defineProps<{
|
||||
// 文档 id
|
||||
current?: Docs | null;
|
||||
}>();
|
||||
|
||||
// 插件
|
||||
const plugins = ref([gfm(), highlight()]);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 编辑器内容,双向绑定
|
||||
const content = ref('');
|
||||
// const docsContentId = ref(0);
|
||||
// 是否是修改
|
||||
// const isUpdate = ref(false);
|
||||
// 当前用户信息
|
||||
const userStore = useUserStore();
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
/* 搜索 */
|
||||
// const reload = () => {
|
||||
// getDocsContent(Number(props.current?.docsId))
|
||||
// .then((res) => {
|
||||
// content.value = String(res.content);
|
||||
// docsContentId.value = Number(res.docsContentId);
|
||||
// isUpdate.value = true;
|
||||
// })
|
||||
// .catch(() => {
|
||||
// loading.value = false;
|
||||
// content.value = '';
|
||||
// isUpdate.value = false;
|
||||
// });
|
||||
// };
|
||||
|
||||
// 监听文档 id 变化
|
||||
watch(
|
||||
() => props.current?.docsId,
|
||||
() => {
|
||||
content.value = String(props.current?.content);
|
||||
}
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,166 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-card :bordered="false" :body-style="{ padding: '16px' }">
|
||||
<ele-split-layout
|
||||
width="266px"
|
||||
allow-collapse
|
||||
:right-style="{ overflow: 'hidden' }"
|
||||
:style="{ minHeight: 'calc(100vh - 152px)' }"
|
||||
>
|
||||
<div>
|
||||
<ele-toolbar theme="default">
|
||||
<a-space :size="10">
|
||||
<a-input-search
|
||||
allow-clear
|
||||
placeholder="搜索关键词"
|
||||
v-model:value="searchText"
|
||||
@pressEnter="search"
|
||||
@search="search"
|
||||
/>
|
||||
</a-space>
|
||||
</ele-toolbar>
|
||||
<div class="ele-border-split sys-docs-list">
|
||||
<a-tree
|
||||
:tree-data="data"
|
||||
v-model:expanded-keys="expandedRowKeys"
|
||||
v-model:selected-keys="selectedRowKeys"
|
||||
@select="onTreeSelect"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<template #content>
|
||||
<content :data="data" :current="current" @done="query"></content>
|
||||
</template>
|
||||
</ele-split-layout>
|
||||
</a-card>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, nextTick, unref } from 'vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { eachTreeData, toTreeData } from "ele-admin-pro";
|
||||
import Content from './content.vue';
|
||||
import { listDocs } from '@/api/cms/docs';
|
||||
import type { Docs, DocsParam } from '@/api/cms/docs/model';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import { useRouter } from 'vue-router';
|
||||
const ROUTE_PATH = '/dashboard/docs';
|
||||
|
||||
const { push, currentRoute } = useRouter();
|
||||
// 加载状态
|
||||
const loading = ref(true);
|
||||
// 树形数据
|
||||
const data = ref<Docs[]>([]);
|
||||
// 树展开的key
|
||||
const expandedRowKeys = ref<number[]>([]);
|
||||
// 树选中的key
|
||||
const selectedRowKeys = ref<number[]>([]);
|
||||
// 选中数据
|
||||
const current = ref<Docs | null>(null);
|
||||
// 默认文档ID
|
||||
const docsId = ref(28);
|
||||
// 搜索内容
|
||||
const searchText = ref('');
|
||||
// 表单数据
|
||||
const { where } = useSearch<DocsParam>({
|
||||
title: ''
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const search = () => {
|
||||
where.title = searchText.value;
|
||||
getDocs();
|
||||
};
|
||||
|
||||
/* 查询 */
|
||||
const getDocs = () => {
|
||||
loading.value = true;
|
||||
listDocs(where)
|
||||
.then((list) => {
|
||||
loading.value = false;
|
||||
const eks: number[] = [];
|
||||
list.forEach((d) => {
|
||||
d.key = d.docsId;
|
||||
d.value = d.docsId;
|
||||
if (typeof d.docsId === 'number') {
|
||||
eks.push(d.docsId);
|
||||
}
|
||||
// 查找当前文档
|
||||
if (d.docsId === docsId.value) {
|
||||
current.value = d;
|
||||
selectedRowKeys.value = [d.docsId];
|
||||
}
|
||||
});
|
||||
expandedRowKeys.value = eks;
|
||||
data.value = toTreeData({
|
||||
data: list,
|
||||
idField: 'docsId',
|
||||
parentIdField: 'parentId'
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 选择数据 */
|
||||
const onTreeSelect = () => {
|
||||
eachTreeData(data.value, (d) => {
|
||||
if (
|
||||
typeof d.docsId === 'number' &&
|
||||
selectedRowKeys.value.includes(d.docsId)
|
||||
) {
|
||||
current.value = d;
|
||||
return false;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
// /* 选择数据 */
|
||||
// const setKey = (item) => {
|
||||
// current.value = item;
|
||||
// selectedRowKeys.value = [item.docsId];
|
||||
// const path = '/dashboard/docs';
|
||||
// nextTick(() => {
|
||||
// push({
|
||||
// path,
|
||||
// query: item ? { id: item.docsId } : undefined
|
||||
// });
|
||||
// });
|
||||
// };
|
||||
|
||||
watch(
|
||||
currentRoute,
|
||||
(route) => {
|
||||
const { fullPath } = unref(route);
|
||||
console.log(ROUTE_PATH);
|
||||
if (!fullPath.startsWith(ROUTE_PATH)) {
|
||||
return;
|
||||
}
|
||||
const { query } = unref(currentRoute);
|
||||
if (query.id) {
|
||||
docsId.value = Number(query.id);
|
||||
}
|
||||
getDocs();
|
||||
},
|
||||
{ immediate: true }
|
||||
);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Docs'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.sys-docs-list {
|
||||
padding: 12px 6px;
|
||||
height: calc(100vh - 242px);
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
overflow: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,158 @@
|
||||
<!-- 角色编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
:width="460"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '修改角色' : '添加角色'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@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="roleName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入角色名称"
|
||||
v-model:value="form.roleName"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="角色标识" name="roleCode">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入角色标识"
|
||||
v-model:value="form.roleCode"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="备注">
|
||||
<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 { addRole, updateRole } from '@/api/system/role';
|
||||
import type { Role } from '@/api/system/role/model';
|
||||
|
||||
// 是否开启响应式布局
|
||||
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?: Role | null;
|
||||
}>();
|
||||
|
||||
//
|
||||
const formRef = ref<FormInstance | null>(null);
|
||||
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
|
||||
// 表单数据
|
||||
const { form, resetFields, assignFields } = useFormData<Role>({
|
||||
roleId: undefined,
|
||||
roleName: '',
|
||||
roleCode: '',
|
||||
comments: ''
|
||||
});
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive<Record<string, Rule[]>>({
|
||||
roleName: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入角色名称',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
roleCode: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入角色标识',
|
||||
type: 'string',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
if (!formRef.value) {
|
||||
return;
|
||||
}
|
||||
formRef.value
|
||||
.validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const saveOrUpdate = isUpdate.value ? updateRole : addRole;
|
||||
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);
|
||||
isUpdate.value = true;
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
formRef.value?.clearValidate();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
@@ -0,0 +1,164 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<!-- 编辑弹窗 -->
|
||||
<comment-edit v-model:visible="showEdit" :data="current" @done="reload" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { createVNode, ref } from 'vue';
|
||||
import { message, Modal } from 'ant-design-vue/es';
|
||||
import { ExclamationCircleOutlined } from '@ant-design/icons-vue';
|
||||
import type { EleProTable } from 'ele-admin-pro/es';
|
||||
import type {
|
||||
DatasourceFunction,
|
||||
ColumnItem
|
||||
} from 'ele-admin-pro/es/ele-pro-table/types';
|
||||
import { messageLoading, toDateString } from 'ele-admin-pro/es';
|
||||
import CommentEdit from './components/comment-edit.vue';
|
||||
import {
|
||||
pageComments,
|
||||
removeComment,
|
||||
removeComments
|
||||
} from '@/api/goods/comment';
|
||||
import type { Comment, CommentParam } from '@/api/goods/comment/model';
|
||||
|
||||
// 表格实例
|
||||
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: 'goods',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '发布者',
|
||||
dataIndex: 'user',
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '描述',
|
||||
dataIndex: 'score',
|
||||
sorter: true,
|
||||
showSorterTooltip: false
|
||||
},
|
||||
{
|
||||
title: '大小',
|
||||
dataIndex: 'content'
|
||||
},
|
||||
{
|
||||
title: '发布时间',
|
||||
dataIndex: 'createTime',
|
||||
sorter: true,
|
||||
showSorterTooltip: false,
|
||||
ellipsis: true,
|
||||
customRender: ({ text }) => toDateString(text)
|
||||
},
|
||||
{
|
||||
title: '操作',
|
||||
key: 'action',
|
||||
width: 200,
|
||||
align: 'center'
|
||||
}
|
||||
]);
|
||||
|
||||
// 表格选中数据
|
||||
const selection = ref<Comment[]>([]);
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<Comment | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
const type = ref('goodsName');
|
||||
const searchText = ref('');
|
||||
const score = ref(0);
|
||||
|
||||
const handleTabs = (e) => {
|
||||
score.value = e.target.value;
|
||||
};
|
||||
|
||||
// 表格数据源
|
||||
const datasource: DatasourceFunction = ({ page, limit, where, orders }) => {
|
||||
if (type.value == 'goodsName') {
|
||||
where.goodsName = searchText.value;
|
||||
}
|
||||
if (type.value == 'goodsCode') {
|
||||
where.goodsCode = searchText.value;
|
||||
}
|
||||
if (type.value == 'userId') {
|
||||
where.userId = searchText.value;
|
||||
}
|
||||
return pageComments({ ...where, ...orders, page, limit });
|
||||
};
|
||||
|
||||
/* 搜索 */
|
||||
const reload = (where?: CommentParam) => {
|
||||
selection.value = [];
|
||||
tableRef?.value?.reload({ page: 1, where });
|
||||
};
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Comment) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
/* 删除单个 */
|
||||
const remove = (row: Comment) => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeComment(row.commentId)
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 批量删除 */
|
||||
const removeBatch = () => {
|
||||
if (!selection.value.length) {
|
||||
message.error('请至少选择一条数据');
|
||||
return;
|
||||
}
|
||||
Modal.confirm({
|
||||
title: '提示',
|
||||
content: '确定要删除选中的评价吗?',
|
||||
icon: createVNode(ExclamationCircleOutlined),
|
||||
maskClosable: true,
|
||||
onOk: () => {
|
||||
const hide = messageLoading('请求中..', 0);
|
||||
removeComments(selection.value.map((d) => d.commentId))
|
||||
.then((msg) => {
|
||||
hide();
|
||||
message.success(msg);
|
||||
reload();
|
||||
})
|
||||
.catch((e) => {
|
||||
hide();
|
||||
message.error(e.message);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ShopComment'
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,268 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
width="680px"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '编辑链接' : '添加链接'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 4 }, sm: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 18 }, sm: { span: 24 } }"
|
||||
layout="horizontal"
|
||||
>
|
||||
<a-form-item label="类型" v-bind="validateInfos.type">
|
||||
<dict-radio
|
||||
v-model:value="form.type"
|
||||
:dictCode="'linkType'"
|
||||
:placeholder="'请选择链接类型'"
|
||||
@change="onChange"
|
||||
@blur="validate('type', { trigger: 'blur' }).catch(() => {})"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="链接名称" v-bind="validateInfos.linkName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入链接名称"
|
||||
v-model:value="form.linkName"
|
||||
@blur="validate('linkName', { trigger: 'blur' }).catch(() => {})"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="描述" v-bind="validateInfos.comments">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入备注"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="form.type === '0'"
|
||||
label="组件路径"
|
||||
v-bind="validateInfos.linkComponent"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入组件路径"
|
||||
v-model:value="form.linkComponent"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="form.type === '1'"
|
||||
label="链接地址"
|
||||
v-bind="validateInfos.linkUrl"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入官方网址"
|
||||
v-model:value="form.linkUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="图标路径" v-bind="validateInfos.linkIcon">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请填写图标路径"
|
||||
v-model:value="form.linkIcon"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="链接分类"
|
||||
v-bind="validateInfos.category"
|
||||
v-if="form.type === '1'"
|
||||
>
|
||||
<dict-select
|
||||
v-model:value="form.category"
|
||||
:dictCode="'linkCategory'"
|
||||
:placeholder="'请选择链接类别'"
|
||||
@blur="validate('category', { trigger: 'blur' }).catch(() => {})"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="下载地址"
|
||||
v-bind="validateInfos.linkDown"
|
||||
v-if="form.type === '1'"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入下载地址"
|
||||
v-model:value="form.linkDown"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="访问账号"
|
||||
v-bind="validateInfos.linkAccount"
|
||||
v-if="form.type === '1'"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请填写访问账号"
|
||||
v-model:value="form.linkAccount"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="访问密码"
|
||||
v-bind="validateInfos.linkPassword"
|
||||
v-if="form.type === '1'"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请填写访问密码"
|
||||
v-model:value="form.linkPassword"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" v-bind="validateInfos.sortNumber">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="排序"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import DictSelect from './dict-select.vue';
|
||||
import DictRadio from './dict-radio.vue';
|
||||
import { addLink, updateLink } from '@/api/apps/link';
|
||||
import type { Link } from '@/api/apps/link/model';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
// 是否显示
|
||||
const isShow = ref(false);
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Link | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 用户信息
|
||||
const form = reactive<Link>({
|
||||
linkId: 0,
|
||||
linkName: '',
|
||||
linkUrl: '',
|
||||
linkAccount: '',
|
||||
linkPassword: '',
|
||||
linkDown: '',
|
||||
linkIcon: '',
|
||||
type: '0',
|
||||
category: '',
|
||||
clicks: undefined,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
linkName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入链接名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
comments: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入功能描述',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
type: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请选项链接类型',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
linkUrl: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写组件路径或链接地址',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* openType选择改变 */
|
||||
const onChange = () => {
|
||||
isShow.value = !isShow.value;
|
||||
};
|
||||
|
||||
const { resetFields, validate, validateInfos } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const data = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateLink : addLink;
|
||||
saveOrUpdate(data)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
loading.value = false;
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
isShow.value = props.data.type === '0';
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="less">
|
||||
.tab-pane {
|
||||
min-height: 300px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,42 @@
|
||||
<!-- 类型选择 -->
|
||||
<template>
|
||||
<a-radio-group
|
||||
:value="value"
|
||||
@blur="onBlur"
|
||||
@update:value="updateValue"
|
||||
:options="data"
|
||||
/>
|
||||
</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;
|
||||
placeholder?: string;
|
||||
dictCode?: string;
|
||||
}>(),
|
||||
{
|
||||
placeholder: '请选择'
|
||||
}
|
||||
);
|
||||
|
||||
// 字典数据
|
||||
const data = getDictionaryOptions(props.dictCode);
|
||||
|
||||
/* 更新选中数据 */
|
||||
const updateValue = (value: string) => {
|
||||
emit('update:value', value);
|
||||
};
|
||||
|
||||
/* 失去焦点 */
|
||||
const onBlur = () => {
|
||||
emit('blur');
|
||||
};
|
||||
</script>
|
||||
@@ -0,0 +1,64 @@
|
||||
<!-- 角色选择下拉框 -->
|
||||
<template>
|
||||
<a-select
|
||||
show-search
|
||||
optionFilterProp="label"
|
||||
:options="data"
|
||||
allow-clear
|
||||
:value="value"
|
||||
: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>
|
||||
@@ -0,0 +1,268 @@
|
||||
<!-- 用户编辑弹窗 -->
|
||||
<template>
|
||||
<ele-modal
|
||||
width="680px"
|
||||
:visible="visible"
|
||||
:confirm-loading="loading"
|
||||
:title="isUpdate ? '编辑链接' : '添加链接'"
|
||||
:body-style="{ paddingBottom: '8px' }"
|
||||
@update:visible="updateVisible"
|
||||
@ok="save"
|
||||
>
|
||||
<a-form
|
||||
:label-col="{ md: { span: 4 }, sm: { span: 24 } }"
|
||||
:wrapper-col="{ md: { span: 18 }, sm: { span: 24 } }"
|
||||
layout="horizontal"
|
||||
>
|
||||
<a-form-item label="类型" v-bind="validateInfos.type">
|
||||
<dict-radio
|
||||
v-model:value="form.type"
|
||||
:dictCode="'linkType'"
|
||||
:placeholder="'请选择链接类型'"
|
||||
@change="onChange"
|
||||
@blur="validate('type', { trigger: 'blur' }).catch(() => {})"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="链接名称" v-bind="validateInfos.linkName">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="请输入链接名称"
|
||||
v-model:value="form.linkName"
|
||||
@blur="validate('linkName', { trigger: 'blur' }).catch(() => {})"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="描述" v-bind="validateInfos.comments">
|
||||
<a-textarea
|
||||
:rows="4"
|
||||
:maxlength="200"
|
||||
placeholder="请输入备注"
|
||||
v-model:value="form.comments"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="form.type === '0'"
|
||||
label="组件路径"
|
||||
v-bind="validateInfos.linkComponent"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入组件路径"
|
||||
v-model:value="form.linkComponent"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
v-if="form.type === '1'"
|
||||
label="链接地址"
|
||||
v-bind="validateInfos.linkUrl"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入官方网址"
|
||||
v-model:value="form.linkUrl"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="图标路径" v-bind="validateInfos.linkIcon">
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请填写图标路径"
|
||||
v-model:value="form.linkIcon"
|
||||
/>
|
||||
</a-form-item>
|
||||
|
||||
<a-form-item
|
||||
label="链接分类"
|
||||
v-bind="validateInfos.category"
|
||||
v-if="form.type === '1'"
|
||||
>
|
||||
<dict-select
|
||||
v-model:value="form.category"
|
||||
:dictCode="'linkCategory'"
|
||||
:placeholder="'请选择链接类别'"
|
||||
@blur="validate('category', { trigger: 'blur' }).catch(() => {})"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="下载地址"
|
||||
v-bind="validateInfos.linkDown"
|
||||
v-if="form.type === '1'"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请输入下载地址"
|
||||
v-model:value="form.linkDown"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="访问账号"
|
||||
v-bind="validateInfos.linkAccount"
|
||||
v-if="form.type === '1'"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请填写访问账号"
|
||||
v-model:value="form.linkAccount"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
label="访问密码"
|
||||
v-bind="validateInfos.linkPassword"
|
||||
v-if="form.type === '1'"
|
||||
>
|
||||
<a-input
|
||||
allow-clear
|
||||
placeholder="请填写访问密码"
|
||||
v-model:value="form.linkPassword"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item label="排序" v-bind="validateInfos.sortNumber">
|
||||
<a-input
|
||||
allow-clear
|
||||
:maxlength="20"
|
||||
placeholder="排序"
|
||||
v-model:value="form.sortNumber"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</ele-modal>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive, watch } from 'vue';
|
||||
import { Form, message } from 'ant-design-vue';
|
||||
import { assignObject } from 'ele-admin-pro';
|
||||
import DictSelect from './dict-select.vue';
|
||||
import DictRadio from './dict-radio.vue';
|
||||
import { addLink, updateLink } from '@/api/apps/link';
|
||||
import type { Link } from '@/api/apps/link/model';
|
||||
|
||||
const useForm = Form.useForm;
|
||||
// 是否是修改
|
||||
const isUpdate = ref(false);
|
||||
// 是否显示
|
||||
const isShow = ref(false);
|
||||
const props = defineProps<{
|
||||
// 弹窗是否打开
|
||||
visible: boolean;
|
||||
// 修改回显的数据
|
||||
data?: Link | null;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'done'): void;
|
||||
(e: 'update:visible', visible: boolean): void;
|
||||
}>();
|
||||
|
||||
// 提交状态
|
||||
const loading = ref(false);
|
||||
// 用户信息
|
||||
const form = reactive<Link>({
|
||||
linkId: 0,
|
||||
linkName: '',
|
||||
linkUrl: '',
|
||||
linkAccount: '',
|
||||
linkPassword: '',
|
||||
linkDown: '',
|
||||
linkIcon: '',
|
||||
type: '0',
|
||||
category: '',
|
||||
clicks: undefined,
|
||||
comments: '',
|
||||
sortNumber: 100
|
||||
});
|
||||
|
||||
/* 更新visible */
|
||||
const updateVisible = (value: boolean) => {
|
||||
emit('update:visible', value);
|
||||
};
|
||||
|
||||
// 表单验证规则
|
||||
const rules = reactive({
|
||||
linkName: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入链接名称',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
comments: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请输入功能描述',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
type: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请选项链接类型',
|
||||
trigger: 'blur'
|
||||
}
|
||||
],
|
||||
linkUrl: [
|
||||
{
|
||||
required: true,
|
||||
type: 'string',
|
||||
message: '请填写组件路径或链接地址',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
/* openType选择改变 */
|
||||
const onChange = () => {
|
||||
isShow.value = !isShow.value;
|
||||
};
|
||||
|
||||
const { resetFields, validate, validateInfos } = useForm(form, rules);
|
||||
|
||||
/* 保存编辑 */
|
||||
const save = () => {
|
||||
validate()
|
||||
.then(() => {
|
||||
loading.value = true;
|
||||
const data = {
|
||||
...form
|
||||
};
|
||||
const saveOrUpdate = isUpdate.value ? updateLink : addLink;
|
||||
saveOrUpdate(data)
|
||||
.then((msg) => {
|
||||
loading.value = false;
|
||||
message.success(msg);
|
||||
updateVisible(false);
|
||||
emit('done');
|
||||
})
|
||||
.catch((e) => {
|
||||
loading.value = false;
|
||||
message.error(e.message);
|
||||
});
|
||||
})
|
||||
.catch(() => {});
|
||||
};
|
||||
|
||||
watch(
|
||||
() => props.visible,
|
||||
(visible) => {
|
||||
if (visible) {
|
||||
if (props.data) {
|
||||
loading.value = false;
|
||||
assignObject(form, props.data);
|
||||
isUpdate.value = true;
|
||||
isShow.value = props.data.type === '0';
|
||||
} else {
|
||||
isUpdate.value = false;
|
||||
}
|
||||
} else {
|
||||
resetFields();
|
||||
}
|
||||
}
|
||||
);
|
||||
</script>
|
||||
<style lang="less">
|
||||
.tab-pane {
|
||||
min-height: 300px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,178 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:lg="6"
|
||||
:md="8"
|
||||
:sm="12"
|
||||
:xs="24"
|
||||
>
|
||||
<a-badge-ribbon :text="item.category">
|
||||
<a-card :bordered="false" hoverable style="margin-top: 16px">
|
||||
<div class="ele-cell" style="margin-bottom: 16px">
|
||||
<a-avatar :src="item.linkIcon" :size="50" />
|
||||
<div class="ele-cell-content">
|
||||
<h4 class="link-name">{{ item.linkName }}</h4>
|
||||
<small class="ele-text-placeholder" v-if="item.type === '1'">
|
||||
{{ item.linkUrl }}
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ele-elip" style="margin-bottom: 6px">
|
||||
{{ item.comments }}
|
||||
</div>
|
||||
<div class="ele-elip ele-text-placeholder">{{
|
||||
item.createTime
|
||||
}}</div>
|
||||
<template #actions>
|
||||
<a-tooltip title="访问链接">
|
||||
<chrome-outlined @click="openUrl(item)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip title="复制链接">
|
||||
<share-alt-outlined @click="copyUrl(item)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip :title="item.linkDown">
|
||||
<download-outlined @click="downUrl(item)" />
|
||||
</a-tooltip>
|
||||
<a-tooltip title="编辑">
|
||||
<edit-outlined @click="openEdit(item)" />
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-card>
|
||||
</a-badge-ribbon>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="ele-text-center" style="margin-top: 18px">
|
||||
<a-pagination
|
||||
:total="where.count"
|
||||
v-model:current="where.page"
|
||||
v-model:page-size="where.limit"
|
||||
@change="onChange"
|
||||
/>
|
||||
</div>
|
||||
<div v-if="!list.length" style="margin-top: 46px">
|
||||
<a-empty />
|
||||
</div>
|
||||
<!-- 编辑弹窗 -->
|
||||
<link-edit v-model:visible="showEdit" :data="current" @done="onSearch" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { PlusOutlined } from '@ant-design/icons-vue';
|
||||
import { pageLink } from '@/api/apps/link';
|
||||
import type { Link, LinkParam } from '@/api/apps/link/model';
|
||||
import LinkEdit from './components/link-edit.vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import {
|
||||
ChromeOutlined,
|
||||
EyeOutlined,
|
||||
DownloadOutlined,
|
||||
EditOutlined,
|
||||
ShareAltOutlined,
|
||||
EllipsisOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import useSearch from '@/utils/use-search';
|
||||
import router from '@/router';
|
||||
import { getDictionaryOptions } from '@/utils/common';
|
||||
|
||||
// 当前编辑数据
|
||||
const current = ref<Link | null>(null);
|
||||
|
||||
// 是否显示编辑弹窗
|
||||
const showEdit = ref(false);
|
||||
|
||||
const linkType = getDictionaryOptions('linkType');
|
||||
|
||||
// 打开链接
|
||||
const openUrl = (item) => {
|
||||
if (item.type == '0') {
|
||||
return router.push(item.linkComponent);
|
||||
}
|
||||
return window.open(item.linkUrl);
|
||||
};
|
||||
|
||||
// 下载文件
|
||||
const downUrl = (url) => {
|
||||
if (url.linkDown) {
|
||||
return window.open(url.linkDown);
|
||||
}
|
||||
};
|
||||
|
||||
// 复制文本
|
||||
const copyUrl = (item) => {
|
||||
// 模拟 输入框
|
||||
const cInput = document.createElement('input');
|
||||
cInput.value = item.linkDown ? item.linkDown : item.linkUrl;
|
||||
document.body.appendChild(cInput);
|
||||
cInput.select(); // 选取文本框内容
|
||||
|
||||
// 执行浏览器复制命令
|
||||
// 复制命令会将当前选中的内容复制到剪切板中(这里就是创建的input标签)
|
||||
// Input要在正常的编辑状态下原生复制方法才会生效
|
||||
message.success('复制成功');
|
||||
document.execCommand('copy');
|
||||
|
||||
// 复制成功后再将构造的标签 移除
|
||||
document.body.removeChild(cInput);
|
||||
};
|
||||
|
||||
const list = ref<Link[]>([]);
|
||||
|
||||
/* 打开编辑弹窗 */
|
||||
const openEdit = (row?: Link) => {
|
||||
current.value = row ?? null;
|
||||
showEdit.value = true;
|
||||
};
|
||||
|
||||
// 表单数据
|
||||
const { where } = useSearch<LinkParam>({
|
||||
page: 1,
|
||||
limit: 36,
|
||||
count: 0,
|
||||
linkName: '',
|
||||
list: []
|
||||
});
|
||||
|
||||
/* 搜索 */
|
||||
const onSearch = (linkName: string) => {
|
||||
// 重置当前页面
|
||||
if (linkName) {
|
||||
where.page = 1;
|
||||
}
|
||||
pageLink({ ...where, linkName }).then((data) => {
|
||||
where.count = data?.count;
|
||||
list.value = data.list;
|
||||
});
|
||||
};
|
||||
|
||||
// 翻页
|
||||
const onChange = (e) => {
|
||||
where.page = e;
|
||||
onSearch();
|
||||
};
|
||||
|
||||
onSearch();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Links'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.app-btn-search {
|
||||
margin-left: 12px;
|
||||
height: 40px;
|
||||
}
|
||||
.link-name {
|
||||
line-height: 1;
|
||||
}
|
||||
.a-link {
|
||||
margin-right: 10px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,304 @@
|
||||
<template>
|
||||
<div class="ele-body">
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-for="(item, index) in data"
|
||||
:key="index"
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 8, md: 12, sm: 12, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-card :bordered="false" hoverable style="margin-top: 16px">
|
||||
<template #cover>
|
||||
<img :src="item.cover" alt="" />
|
||||
</template>
|
||||
<a-card-meta :title="item.title">
|
||||
<template #description>
|
||||
<div class="project-list-desc" :title="item.content">
|
||||
{{ item.content }}
|
||||
</div>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
<div class="ele-cell">
|
||||
<div class="ele-cell-content ele-text-secondary">
|
||||
{{ item.time }}
|
||||
</div>
|
||||
<ele-avatar-list :data="item.users" size="small" />
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<div class="ele-text-center" style="margin-top: 18px">
|
||||
<a-pagination
|
||||
:total="count"
|
||||
v-model:current="page"
|
||||
v-model:page-size="limit"
|
||||
@change="query"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
|
||||
interface UserType {
|
||||
name: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
interface DataType {
|
||||
title: string;
|
||||
content: string;
|
||||
time: string;
|
||||
cover: string;
|
||||
users: UserType[];
|
||||
}
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
const data = ref<DataType[]>([]);
|
||||
|
||||
const keyword = ref('');
|
||||
|
||||
// 第几页
|
||||
const page = ref(1);
|
||||
|
||||
// 每页多少条
|
||||
const limit = ref(8);
|
||||
|
||||
// 总数量
|
||||
const count = ref(0);
|
||||
|
||||
/* 查询数据 */
|
||||
const query = () => {
|
||||
count.value = 40;
|
||||
data.value = [
|
||||
{
|
||||
title: 'ElementUI',
|
||||
content:
|
||||
'Element,一套为开发者、设计师和产品经理准备的基于 Vue 2.0 的组件库,提供了配套设计资源,帮助你的网站快速成型。',
|
||||
time: '2 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/RZ8FQmZfHkcffMlTBCJllBFjEhEsObVo.jpg',
|
||||
users: [
|
||||
{
|
||||
name: 'SunSmile',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg'
|
||||
},
|
||||
{
|
||||
name: '酷酷的大叔',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg'
|
||||
},
|
||||
{
|
||||
name: 'Jasmine',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Vue.js',
|
||||
content:
|
||||
'Vue 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。',
|
||||
time: '4 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/WLXm7gp1EbLDtvVQgkeQeyq5OtDm00Jd.jpg',
|
||||
users: [
|
||||
{
|
||||
name: 'SunSmile',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg'
|
||||
},
|
||||
{
|
||||
name: '酷酷的大叔',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg'
|
||||
},
|
||||
{
|
||||
name: 'Jasmine',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Vuex',
|
||||
content:
|
||||
'Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式。它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化。',
|
||||
time: '12 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/4Z0QR2L0J1XStxBh99jVJ8qLfsGsOgjU.jpg',
|
||||
users: [
|
||||
{
|
||||
name: 'SunSmile',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg'
|
||||
},
|
||||
{
|
||||
name: '酷酷的大叔',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg'
|
||||
},
|
||||
{
|
||||
name: 'Jasmine',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Vue Router',
|
||||
content:
|
||||
'Vue Router 是 Vue.js 官方的路由管理器。它和 Vue.js 的核心深度集成,让构建单页面应用变得易如反掌。',
|
||||
time: '14 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/ttkIjNPlVDuv4lUTvRX8GIlM2QqSe0jg.jpg',
|
||||
users: [
|
||||
{
|
||||
name: 'SunSmile',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg'
|
||||
},
|
||||
{
|
||||
name: '酷酷的大叔',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg'
|
||||
},
|
||||
{
|
||||
name: 'Jasmine',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Sass',
|
||||
content: 'Sass 是世界上最成熟、稳定、强大的专业级 CSS 扩展语言。',
|
||||
time: '10 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/fAenQ8nvRjL7x0i0jEfuDBZHvJfHf3v6.jpg',
|
||||
users: [
|
||||
{
|
||||
name: 'SunSmile',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg'
|
||||
},
|
||||
{
|
||||
name: '酷酷的大叔',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg'
|
||||
},
|
||||
{
|
||||
name: 'Jasmine',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Axios',
|
||||
content:
|
||||
'Axios 是一个基于 promise 的 HTTP 库,可以用在浏览器和 node.js 中。',
|
||||
time: '16 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/LrCTN2j94lo9N7wEql7cBr1Ux4rHMvmZ.jpg',
|
||||
users: [
|
||||
{
|
||||
name: 'SunSmile',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg'
|
||||
},
|
||||
{
|
||||
name: '酷酷的大叔',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg'
|
||||
},
|
||||
{
|
||||
name: 'Jasmine',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Webpack',
|
||||
content:
|
||||
'webpack 是一个模块打包器。webpack 的主要目标是将 JavaScript 文件打包在一起,打包后的文件用于在浏览器中使用。',
|
||||
time: '6 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/yeKvhT20lMU0f1T3Y743UlGEOLLnZSnp.jpg',
|
||||
users: [
|
||||
{
|
||||
name: 'SunSmile',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg'
|
||||
},
|
||||
{
|
||||
name: '酷酷的大叔',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg'
|
||||
},
|
||||
{
|
||||
name: 'Jasmine',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
title: 'Node.js',
|
||||
content:
|
||||
'Node.js 是一个基于 Chrome V8 引擎的 JavaScript 运行环境。Node.js 使用了一个事件驱动、非阻塞式 I/O 的模型,使其轻量又高效。',
|
||||
time: '8 小时前',
|
||||
cover:
|
||||
'https://cdn.eleadmin.com/20200610/CyrCNmTJfv7D6GFAg39bjT3eRkkRm5dI.jpg',
|
||||
users: [
|
||||
{
|
||||
name: 'SunSmile',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg'
|
||||
},
|
||||
{
|
||||
name: '酷酷的大叔',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg'
|
||||
},
|
||||
{
|
||||
name: 'Jasmine',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
query();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ListCardProject'
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.project-list-desc {
|
||||
height: 44px;
|
||||
line-height: 22px;
|
||||
margin-bottom: 20px;
|
||||
overflow: hidden;
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,378 @@
|
||||
<template>
|
||||
<div class="ele-body ele-body-card">
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive ? { lg: 12, md: 24, sm: 24, xs: 24 } : { span: 12 }
|
||||
"
|
||||
>
|
||||
<a-card title="基础演示" :bordered="false">
|
||||
<!-- 操作按钮 -->
|
||||
<a-space style="margin-bottom: 16px">
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!ready1"
|
||||
class="ele-btn-icon"
|
||||
@click="play"
|
||||
>
|
||||
播放
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!ready1"
|
||||
class="ele-btn-icon"
|
||||
@click="pause"
|
||||
>
|
||||
暂停
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!ready1"
|
||||
class="ele-btn-icon"
|
||||
@click="replay"
|
||||
>
|
||||
重新播放
|
||||
</a-button>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!ready1"
|
||||
class="ele-btn-icon"
|
||||
@click="changeSrc"
|
||||
>
|
||||
切换视频源
|
||||
</a-button>
|
||||
</a-space>
|
||||
<!-- 播放器 -->
|
||||
<ele-xg-player :config="config1" @player="onPlayer1" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive ? { lg: 12, md: 24, sm: 24, xs: 24 } : { span: 12 }
|
||||
"
|
||||
>
|
||||
<a-card title="显示弹幕" :bordered="false">
|
||||
<!-- 操作按钮 -->
|
||||
<a-space style="margin-bottom: 16px">
|
||||
<a-input
|
||||
style="width: 160px"
|
||||
v-model:value="text"
|
||||
placeholder="请输入弹幕内容"
|
||||
:disabled="!ready2"
|
||||
/>
|
||||
<a-button
|
||||
type="primary"
|
||||
:disabled="!ready2"
|
||||
class="ele-btn-icon"
|
||||
@click="shoot"
|
||||
>
|
||||
发射
|
||||
</a-button>
|
||||
</a-space>
|
||||
<!-- 播放器 -->
|
||||
<ele-xg-player :config="config2" @player="onPlayer2" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import type Player from 'xgplayer';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
// 视频播放器一配置
|
||||
const config1 = reactive({
|
||||
id: 'demoPlayer1',
|
||||
lang: 'zh-cn',
|
||||
fluid: true,
|
||||
// 视频地址
|
||||
url: 'https://s1.pstatp.com/cdn/expire-1-M/byted-player-videos/1.0.0/xgplayer-demo.mp4',
|
||||
// 封面
|
||||
poster:
|
||||
'https://imgcache.qq.com/open_proj/proj_qcloud_v2/gateway/solution/general-video/css/img/scene/1.png',
|
||||
// 开启倍速播放
|
||||
playbackRate: [0.5, 1, 1.5, 2],
|
||||
// 开启画中画
|
||||
pip: true
|
||||
});
|
||||
|
||||
// 视频播放器一是否实例化完成
|
||||
const ready1 = ref(false);
|
||||
|
||||
// 视频播放器二配置
|
||||
const config2 = reactive({
|
||||
id: 'demoPlayer2',
|
||||
lang: 'zh-cn',
|
||||
fluid: true,
|
||||
url: 'https://blz-videos.nosdn.127.net/1/OverWatch/AnimatedShots/Overwatch_TheatricalTeaser_WeAreOverwatch_zhCN.mp4',
|
||||
poster:
|
||||
'https://imgcache.qq.com/open_proj/proj_qcloud_v2/gateway/solution/general-video/css/img/scene/1.png',
|
||||
danmu: {
|
||||
comments: [
|
||||
{
|
||||
id: '1',
|
||||
start: 0,
|
||||
txt: '空降',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '2',
|
||||
start: 1500,
|
||||
txt: '前方高能',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '3',
|
||||
start: 3500,
|
||||
txt: '弹幕护体',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '4',
|
||||
start: 4500,
|
||||
txt: '弹幕护体',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '5',
|
||||
start: 6000,
|
||||
txt: '前方高能',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '6',
|
||||
start: 8500,
|
||||
txt: '弹幕护体',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '7',
|
||||
start: 10000,
|
||||
txt: '666666666',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '8',
|
||||
start: 12500,
|
||||
txt: '前方高能',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '9',
|
||||
start: 15500,
|
||||
txt: '666666666',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '10',
|
||||
start: 16500,
|
||||
txt: '666666666',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '11',
|
||||
start: 18000,
|
||||
txt: '关弹幕,保智商',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '12',
|
||||
start: 20500,
|
||||
txt: '关弹幕,保智商',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '13',
|
||||
start: 22000,
|
||||
txt: '666666666',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '14',
|
||||
start: 25500,
|
||||
txt: '666666666',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
},
|
||||
{
|
||||
id: '15',
|
||||
start: 26000,
|
||||
txt: '前方高能',
|
||||
duration: 15000,
|
||||
color: true,
|
||||
style: {
|
||||
color: '#ffcd08',
|
||||
fontSize: '20px'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
});
|
||||
|
||||
// 视频播放器二是否实例化完成
|
||||
const ready2 = ref(false);
|
||||
|
||||
// 弹幕输入内容
|
||||
const text = ref('');
|
||||
|
||||
// 视频播放器一实例
|
||||
let player1: Player;
|
||||
|
||||
// 视频播放器二实例
|
||||
let player2: Player;
|
||||
|
||||
/* 播放器一渲染完成 */
|
||||
const onPlayer1 = (player: Player) => {
|
||||
player1 = player;
|
||||
player1.on('play', () => {
|
||||
ready1.value = true;
|
||||
});
|
||||
};
|
||||
|
||||
/* 播放 */
|
||||
const play = () => {
|
||||
if (player1 && player1.paused) {
|
||||
player1.play();
|
||||
}
|
||||
};
|
||||
|
||||
/* 暂停 */
|
||||
const pause = () => {
|
||||
if (player1) {
|
||||
player1.pause();
|
||||
}
|
||||
};
|
||||
|
||||
/* 重新播放 */
|
||||
const replay = () => {
|
||||
if (player1) {
|
||||
player1.replay();
|
||||
}
|
||||
};
|
||||
|
||||
/* 切换视频源 */
|
||||
const changeSrc = () => {
|
||||
if (player1) {
|
||||
player1.src =
|
||||
'https://blz-videos.nosdn.127.net/1/OverWatch/AnimatedShots/Overwatch_TheatricalTeaser_WeAreOverwatch_zhCN.mp4';
|
||||
if (player1.paused) {
|
||||
player1.play();
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/* 播放器二渲染完成 */
|
||||
const onPlayer2 = (player: Player) => {
|
||||
player2 = player;
|
||||
player2.on('play', () => {
|
||||
ready2.value = true;
|
||||
});
|
||||
};
|
||||
|
||||
/* 发射弹幕 */
|
||||
const shoot = () => {
|
||||
if (!player2) {
|
||||
return;
|
||||
}
|
||||
if (!text.value) {
|
||||
message.error('请输入弹幕内容');
|
||||
return;
|
||||
}
|
||||
player2.danmu.sendComment({
|
||||
id: new Date().getTime(),
|
||||
duration: 15000,
|
||||
color: true,
|
||||
start: player2.currentTime * 1000,
|
||||
txt: text.value,
|
||||
style: {
|
||||
color: '#fa1f41',
|
||||
fontSize: '20px',
|
||||
border: 'solid 1px #fa1f41'
|
||||
}
|
||||
});
|
||||
text.value = '';
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'ExtensionPlayer'
|
||||
};
|
||||
</script>
|
||||
Reference in New Issue
Block a user