Commit 3889adc7 authored by 白舜's avatar 白舜 🎱

add bg-btns test

parents
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?
{
"recommendations": ["Vue.volar"]
}
{
"eslint.validate": [
"vue",
"javascript",
"javascriptreact"
],
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.codeActionsOnSave": [
"source.formatDocument",
"source.fixAll.eslint"
],
"editor.formatOnSave": false,
"files.eol": "\n",
"path-intellisense.mappings": {
"@": "${workspaceFolder}/src"
},
"volar.codeLens.pugTools": false,
"typescript.preferences.useAliasesForRenames": false,
"liveServer.settings.root": "/dist",
"prettier.printWidth": 120
}
# BG-UI Demo
这是为了方便开发,搭建的demo。
## 运行
```
yarn
```
```
yarn dev
```
## 依赖
主要依赖 `element-plus` ,其中部分组件依赖如下:
```
* bg-filter-date.vue
el-date-picker
* bg-filter-group
el-input el-button
* bg-list
el-button
* bg-pagination
el-pagination
* bg-table
el-table el-table-column
* bg-table-pro
el-table el-table-column
* bg-tags
el-button el-input
* bg-upload-image
el-upload el-icon el-button el-dialog
* bg-upload
el-upload el-button
* bg-user-upload-image
el-upload
```
```
* bg-code-editor
vue3-ace-editor ace-builds
* bg-info
clipboard
* bg-layout-card
html2canvas
* bg-rich-text
@wangeditor/editor @wangeditor/editor-for-vue (vue2)
* bg-upload-image
@element-plus/icons-vue
```
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Vite + Vue</title>
</head>
<body>
<div id="app"></div>
<script type="module" src="/src/main.js"></script>
</body>
</html>
{
"name": "my-vue-app",
"private": true,
"version": "0.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"@element-plus/icons-vue": "^2.0.10",
"@wangeditor/editor": "^5.1.23",
"@wangeditor/editor-for-vue": "^5.1.12",
"ace-builds": "^1.13.1",
"clipboard": "^2.0.11",
"element-plus": "^2.2.25",
"html2canvas": "^1.4.1",
"vue": "^3.2.41",
"vue3-ace-editor": "^2.2.2"
},
"devDependencies": {
"@vitejs/plugin-vue": "^3.2.0",
"sass": "^1.56.1",
"vite": "^3.2.3"
}
}
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="31.88" height="32" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%" y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%" stop-color="#BD34FE"></stop></linearGradient><linearGradient id="IconifyId1813088fe1fbc01fb467" x1="43.376%" x2="50.316%" y1="2.242%" y2="89.03%"><stop offset="0%" stop-color="#FFEA83"></stop><stop offset="8.333%" stop-color="#FFDD35"></stop><stop offset="100%" stop-color="#FFA800"></stop></linearGradient></defs><path fill="url(#IconifyId1813088fe1fbc01fb466)" d="M255.153 37.938L134.897 252.976c-2.483 4.44-8.862 4.466-11.382.048L.875 37.958c-2.746-4.814 1.371-10.646 6.827-9.67l120.385 21.517a6.537 6.537 0 0 0 2.322-.004l117.867-21.483c5.438-.991 9.574 4.796 6.877 9.62Z"></path><path fill="url(#IconifyId1813088fe1fbc01fb467)" d="M185.432.063L96.44 17.501a3.268 3.268 0 0 0-2.634 3.014l-5.474 92.456a3.268 3.268 0 0 0 3.997 3.378l24.777-5.718c2.318-.535 4.413 1.507 3.936 3.838l-7.361 36.047c-.495 2.426 1.782 4.5 4.151 3.78l15.304-4.649c2.372-.72 4.652 1.36 4.15 3.788l-11.698 56.621c-.732 3.542 3.979 5.473 5.943 2.437l1.313-2.028l72.516-144.72c1.215-2.423-.88-5.186-3.54-4.672l-25.505 4.922c-2.396.462-4.435-1.77-3.759-4.114l16.646-57.705c.677-2.35-1.37-4.583-3.769-4.113Z"></path></svg>
\ No newline at end of file
<script setup>
import { reactive } from "vue"
const btns = reactive({
options: [{ name: 'hello', value: 'hello' }, { name: 'world', value: 'world' }],
currentValue: ''
})
</script>
<template>
<bg-btns :options="btns.options" v-model="btns.currentValue"></bg-btns>
</template>
<style scoped>
</style>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img" class="iconify iconify--logos" width="37.07" height="36" preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 198"><path fill="#41B883" d="M204.8 0H256L128 220.8L0 0h97.92L128 51.2L157.44 0h47.36Z"></path><path fill="#41B883" d="m0 0l128 220.8L256 0h-51.2L128 132.48L50.56 0H0Z"></path><path fill="#35495E" d="M50.56 0L128 133.12L204.8 0h-47.36L128 51.2L97.92 0H50.56Z"></path></svg>
\ No newline at end of file
<template>
<div class="bg-btns">
<ul>
<li
v-for="btn in options"
:class="{
'is-active': modelValue === btn.value,
}"
:key="btn.value"
@click="selectBtn(btn)"
>
{{ btn.name }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: "BgBtns",
props: {
modelValue: {
type: [String, Number],
default: "",
},
options: {
type: Array,
default: () => [],
},
},
emits: ["update:modelValue"],
methods: {
selectBtn({ value }) {
this.$emit("update:modelValue", value);
},
},
};
</script>
<template>
<div class="bg-card">
<h3 class="card-title text-clip">
<span class="title-icon" v-if="icon">
<bg-icon :icon="icon" />
</span>
<span class="title-text">{{ title }}</span>
</h3>
<div class="card-content" ref="content">
<slot />
</div>
</div>
</template>
<script>
export default {
name: "BgCard",
props: {
title: {
type: String,
default: "",
},
icon: {
type: String,
default: "",
},
},
};
</script>
<template>
<VAceEditor
v-model:value="states.content"
class="vue-ace-editor"
:class="{'vue-ace-editor-disable':props.disabled}"
@input="codeChange"
:lang="props.lang"
:theme="props.theme"
:options="{
useWorker: true,
readOnly: props.disabled,
wrap: true
}"
/>
</template>
<script setup>
import { reactive, toRefs, watch,onMounted } from "vue";
import { VAceEditor } from "vue3-ace-editor";
import ace from 'ace-builds';
import modeJsonUrl from 'ace-builds/src-noconflict/mode-json?url';
import modeJavascriptUrl from 'ace-builds/src-noconflict/mode-javascript?url';
import modeHtmlUrl from 'ace-builds/src-noconflict/mode-html?url';
import themeGithubUrl from 'ace-builds/src-noconflict/theme-github?url';
import themeChromeUrl from 'ace-builds/src-noconflict/theme-chrome?url';
import themeMonokaiUrl from 'ace-builds/src-noconflict/theme-monokai?url';
import workerBaseUrl from 'ace-builds/src-noconflict/worker-base?url';
import workerJsonUrl from 'ace-builds/src-noconflict/worker-json?url';
import workerJavascriptUrl from 'ace-builds/src-noconflict/worker-javascript?url';
import workerHtmlUrl from 'ace-builds/src-noconflict/worker-html?url';
ace.config.setModuleUrl('ace/mode/json', modeJsonUrl);
ace.config.setModuleUrl('ace/mode/javascript', modeJavascriptUrl);
ace.config.setModuleUrl('ace/mode/html', modeHtmlUrl);
ace.config.setModuleUrl('ace/theme/github', themeGithubUrl);
ace.config.setModuleUrl('ace/theme/chrome', themeChromeUrl);
ace.config.setModuleUrl('ace/theme/monokai', themeMonokaiUrl);
ace.config.setModuleUrl('ace/mode/base', workerBaseUrl);
ace.config.setModuleUrl('ace/mode/json_worker', workerJsonUrl);
ace.config.setModuleUrl('ace/mode/javascript_worker', workerJavascriptUrl);
ace.config.setModuleUrl('ace/mode/html_worker', workerHtmlUrl);
const props = defineProps(
{
modelValue: {
type:String,
default:"",
},
disabled:{
type:Boolean,
default:false
},
// lang:{
// type:String,
// default:"json"
// },
// theme:{
// type:String,
// default:"themeChromeUrl"
// },
width:{
type:String,
default:"100%"
},
}
)
const emit = defineEmits(['update:modelValue'])
// watch(
// props.modelValue,
// (n,o) => {
// states.content = n
// }
// )
const states = reactive({
lang: "json",
theme: "github",
content: "",
});
// watch(
// states.content,
// (n,o) => {
// emit("update:modelValue", n);
// }
// )
const codeChange = ()=>{
emit("update:modelValue", states.content);
}
onMounted(() => {
let obj = "";
// console.log(typeof JSON.parse(this.datas));
try {
if (typeof JSON.parse(props.modelValue) == "object") {
obj = JSON.stringify(JSON.parse(props.modelValue), null, "\t");
}else{
obj = props.modelValue;
}
} catch (e) {
obj = props.modelValue;
}
states.content = obj
})
const {content} = toRefs(states)
</script>
<style scoped>
.vue-ace-editor {
flex: 1;
margin-top: 15px;
font-size: 16px;
border: 1px solid #dadee7;
width: 100%;
height: 100%;
border-radius: 4px;
overflow: hidden;
}
.vue-ace-editor :deep() .ace_scrollbar-v{
width: 0px!important;
}
.vue-ace-editor :deep() .ace_gutter{
font-size: 14px;
color: #ffffff;
background-color: #262626;
}
.vue-ace-editor :deep() .ace_gutter-cell{
line-height: 22px;
background-color: #262626;
}
.vue-ace-editor :deep() .ace_print-margin{
width: 0;
}
.vue-ace-editor :deep() .ace_scroller{
background-color: #1a1a1a;
color: #fff;
caret-color:#fff;
right: 0!important;
}
/* 光标颜色 */
.vue-ace-editor :deep() .ace_cursor{
color: #fff;
}
.vue-ace-editor-disable :deep() .ace_scrollbar-v{
width: 6px!important;
}
.vue-ace-editor-disable :deep() .ace_gutter{
background-color: #202531;
}
.vue-ace-editor-disable :deep() .ace_gutter-cell{
background-color: #202531;
}
.vue-ace-editor-disable :deep() .ace_scroller{
background-color: #fff;
color: #202531;
}
/* 光标颜色 */
.vue-ace-editor-disable :deep() .ace_cursor{
color: #000;
}
</style>
<template>
<div class="detail_box">
<div class="detail_text text_clip" :style="index==data.length-1?last_width:unit_width" v-for="(item,index) in data" :key="'data'+index">
<span>{{item.title}}</span>
<!-- 拓展功能 -->
<template v-if="item.slot">
<span>
<slot v-bind:item="item" :name="item.slot"></slot>
</span>
</template>
<!-- 原有下载功能 -->
<template v-else>
<span v-if="!item.urls" :title="item.info" @click="down_file(item.url)" :style="item.url?{color:'#515fe7',cursor:'pointer'}:''">{{item.info}}</span>
<span v-else :title="item.info">
<span v-for="(it,idx) in item.urls" @click="down_file(it)" style="color:#515fe7;cursor:pointer;" :key="'urls'+idx">{{helper.downloadFileFormatNew(it)}}</span>
</span>
</template>
</div>
<div class="bg" :style="{top:(2*index+1)*42+'px'}" v-for="(item,index) in bg_num" :key="'bg'+index"></div>
</div>
</template>
<script>
import helper from './utils/index.js'
console.log(helper);
export default {
props: {
data:{
type: Array,
default: () => [],
},
layout:{
line_num:4
}
},
components: {
},
data() {
return {
helper,
unit_width:0,
last_width:0,
bg_num:0,
};
},
watch: {
data:{
handler: function(n, o) {
if(this.layout.line_num){
this.unit_width = {width:100/this.layout.line_num +'%'}
}
if(this.layout.line_num&&n.length%this.layout.line_num!==0){//计算最后一个格子的宽度
this.last_width = {width:(this.layout.line_num-(n.length%this.layout.line_num)+1)/this.layout.line_num*100+'%'}
}else{
this.last_width = {width:100/this.layout.line_num +'%'}
}
if(n.length<this.layout.line_num){
return
}else{
this.bg_num = Math.floor((Math.ceil(n.length/this.layout.line_num))/2)
}
},
immediate: true
}
},
computed: {
},
created() {
},
mounted() {
},
methods: {
down_file(url){
if(url){
console.log(url);
const a = document.createElement("a"); // 创建a标签
a.setAttribute("download", ""); // download属性
a.setAttribute("href", url); // href链接
a.click(); // 自执行点击事件
}
}
},
};
</script>
<style scoped>
.detail_box{
width: 100%;
border-bottom: 1px solid #e3e5ef;
border-right: 1px solid #e3e5ef;
overflow: hidden;
position: relative;
}
.detail_box .detail_text{
width: 25%;
height: 42px;
line-height: 42px;
padding: 0 15px;
border-left: 1px solid #e3e5ef;
border-top: 1px solid #e3e5ef;
float: left;
position: relative;
z-index: 1;
}
.detail_box .detail_text span:nth-of-type(1){
color: #616f94;
}
.detail_box .detail_text span:nth-of-type(2){
color: #404a62;
}
.bg{
background-color:#f7f8f9;
width: 100%;
height: 42px;
position: absolute;
}
</style>
<template>
<div class="out-detail">
<div class="row-box" v-for="(item,index) in list" :style="{width:item.width}" :key="'row-box'+index">
<p class="detail-module" v-if="!item.slot">
<span :style="{width:itemWidth}">{{item.label}}</span>
<span class="text_clip" :title="item.value" v-if="!item.childSlot">{{item.value}}</span>
<span v-else>
<slot :name="item.childSlot" :data="item"></slot>
</span>
</p>
<template v-else>
<slot :name="item.slot" :data="item"></slot>
</template>
</div>
</div>
</template>
<script>
export default {
props: {
list:{
type:Array,
default:()=>[]
},
itemWidth:{
type:String,
default:''
}
},
components: {
},
data() {
return {
};
},
watch: {
},
computed: {
},
created() {
},
mounted() {
},
methods: {
},
};
</script>
<style scoped>
.out-detail{
width: 100%;
overflow: hidden;
display: flex;
flex-wrap: wrap;
border-right: solid 1px #dadee7;
border-bottom: solid 1px #dadee7;
}
.row-box{
width: 50%;
flex-grow:1;
text-align: left;
line-height: 48px;
min-height: 48px;
border-left: solid 1px #dadee7;
border-top: solid 1px #dadee7;
font-size: 14px;
color: #404a62;
}
.row-box .detail-module{
height: 100%;
display: flex;
}
.row-box .detail-module span{
height: 100%;
display: inline-block;
padding-left: 15px;
box-sizing: border-box;
}
.row-box .detail-module span:nth-of-type(1){
background-color: #f7f7f9;
min-width: 114px;
border-right: solid 1px #dadee7;
}
.row-box .detail-module span:nth-of-type(2){
flex-grow:1;
}
</style>
<template>
<div class="bg-detail bg-form">
<!-- 固定导航 -->
<div class="bg-tabs-nav--fixed" v-show="showFixedBars">
<ul v-if="calcTabs().length">
<li
v-for="(item, index) in calcTabs()"
:key="'tab_' + index"
:class="{
current: activeName === item.name,
}"
@click="changeActiveName(item, index)"
>
{{ item.label }}
</li>
<li>
<!-- empty -->
</li>
</ul>
</div>
<!-- 面包屑 -->
<div class="bg-breadcrumb">
<slot name="breadcrumb" />
</div>
<!-- 基本信息 -->
<div class="bg-detail-info" v-if="$slots.info">
<slot name="info" />
</div>
<!-- 兼容意外模块 -->
<div v-if="$slots.other">
<slot name="other" />
</div>
<!-- 详情 -->
<div class="bg-tabs bg-detail-tabs">
<div class="bg-tabs-nav" v-if="calcTabs().length">
<ul>
<li
v-for="(item, index) in calcTabs()"
:key="'tab_' + index"
:class="{
current: activeName === item.name,
}"
@click="changeActiveName(item, index)"
>
{{ item.label }}
</li>
<li>
<!-- empty -->
</li>
</ul>
</div>
<div class="bg-tabs-content">
<slot />
</div>
</div>
</div>
</template>
<script>
export default {
name: "BgDetail",
provide() {
return {
getActiveName: () => {
return this.activeName;
},
getIsTabs: () => {
return false;
},
};
},
data() {
return {
activeName: "",
showFixedBars: false,
scrollCallback: null,
};
},
methods: {
calcTabs() {
let tabSlots = [];
if (this.$slots.default) {
tabSlots = this.$slots.default
.filter(
(vnode) =>
vnode.tag &&
vnode.componentOptions &&
vnode.componentOptions.Ctor.options.name === "BgTab"
)
.map((vnode) => vnode.componentOptions.propsData);
}
return tabSlots;
},
changeActiveName({ name }, index) {
let targetEl = this.$el.querySelectorAll(`.bg-tab`)[index];
let targetCtx = document.querySelector(`.bg-main`);
targetCtx.scrollTop = targetEl && targetEl.offsetTop - 165;
this.activeName = name;
this.scrollCallback = () => {
this.activeName = name;
};
},
scrollAction() {
let targetCtx = document.querySelector(`.bg-main`);
let ctxScrollTop = targetCtx.scrollTop || 0;
let targetEls = this.$el.querySelectorAll(`.bg-tab`);
let tabs = this.calcTabs();
for (let i = 0; i < targetEls.length; i++) {
let targetEl = targetEls[i];
if (ctxScrollTop >= targetEl.offsetTop) {
this.activeName = tabs[i].name;
}
}
this.showFixedBars = ctxScrollTop > 222;
this.scrollCallback && this.scrollCallback();
this.scrollCallback = null;
},
},
mounted() {
this.$nextTick(() => {
let tabs = this.calcTabs();
this.activeName = tabs[0] && tabs[0].name;
this.scrollAction();
window.addEventListener("scroll", this.scrollAction, true);
});
},
destroyed() {
window.removeEventListener("scroll", this.scrollAction, true);
},
};
</script>
<template>
<div class="bg-filtrate">
<span class="bg-filtrate-text">{{ name }}</span>
<div class="bg-filtrate-list">
<el-date-picker
v-model="value"
type="daterange"
value-format="yyyy-MM-dd"
range-separator="~"
start-placeholder="开始日期"
end-placeholder="结束日期"
@change="change">
</el-date-picker>
</div>
</div>
</template>
<script>
export default {
name: "BgFilterDate",
model: {
prop: "time",
event: "change",
},
props: {
name: {
type: String,
default: "",
},
},
data() {
return {
value: ""
}
},
computed: {
},
methods: {
change(event) {
this.$emit("change", event);
},
},
};
</script>
\ No newline at end of file
<template>
<div class="bg-filter-group" :style="{'border-bottom': showFlag? 'none' : '','padding-bottom': showFlag? '8px':'16px'}">
<div class="top-filter">
<div class="left-slot">
<slot name="left_action"></slot>
</div>
<div class="right-filter">
<el-input :placeholder="placeholder" @keydown.enter="search" @clear="search" clearable v-model.trim="modelValue">
<template #append>
<div class="append-btn" @click="search">
<bg-icon style="font-size: 12px; color: #404a62; " icon="#bg-ic-search"></bg-icon>
</div>
</template>
</el-input>
<div class="more-btn" :class="showFlag ? 'more-btn1': ''" v-if="$slots.filter_group">
<el-button type="default" class="more-filter" @click="moreFilter">
高级搜索
<bg-icon style="font-size: 8px; color: #404a62; margin-left: 8px" icon="#bg-ic-arrow-down" v-if="!showFlag"></bg-icon>
<bg-icon style="font-size: 8px; color: #404a62; margin-left: 8px" icon="#bg-ic-arrow-up" v-else></bg-icon>
</el-button>
</div>
</div>
</div>
<div class="filter-group" v-if="showFlag">
<slot name="filter_group"></slot>
</div>
</div>
</template>
<script setup>
import { computed, onMounted, reactive, toRefs, watch,ref } from "vue"
const state = reactive({
showFlag: false,
modelValue: ""
})
const props = defineProps({
modelValue: {
type: String,
default: '',
},
placeholder: {
type: String,
default: "请输入关键词"
}
})
watch(props,(n,o) => {
state.modelValue = n.modelValue
})
watch(() => state.modelValue,(n,o) => {
emit('update:modelValue',n)
})
const emit = defineEmits(['search','update:modelValue'])
const search = () => {
emit('search',state.modelValue)
}
const moreFilter = () => {
state.showFlag = !state.showFlag
}
onMounted(() => {
state.modelValue = props.modelValue
})
const { modelValue,showFlag } = toRefs(state)
</script>
\ No newline at end of file
<template>
<div class="bg-filter" v-if="options.length > 0">
<span>{{ name }}</span>
<ul>
<li
v-for="(item, index) in fullOptions"
:class="{ current: selection.indexOf(item.value) > -1 }"
:key="'li_' + index"
@click="selectAction(item)"
>
{{ item.name }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: "BgFilter",
model: {
prop: "value",
event: "change",
},
props: {
isCalc:{
type:Boolean,
default:false,
},
value: {
type: [Number, String],
default: "",
},
name: {
type: String,
default: "",
},
options: {
type: Array,
default: () => [],
},
optionName: {
type: String,
default: "name",
},
optionValue: {
type: String,
default: "value",
},
multiple: {
type: Boolean,
default: false,
},
},
computed: {
fullOptions() {
return [
{
name: "全部",
value: "",
},
...this.options.map((item) => {
return {
name: item[this.optionName],
value: item[this.optionValue] + "",
sub_cate: item.sub_cate ? item.sub_cate : "",
};
}),
];
},
selection() {
let value = this.value + "";
return value.split(",");
},
},
methods: {
selectAction({ value, name, sub_cate }) {
if (value && this.multiple) {
let selection = [...this.selection].filter((v) => v !== "");
let index = selection.findIndex((v) => v === value);
if (index > -1) {
selection.splice(index, 1);
} else {
selection.push(value);
}
this.$emit("change", selection.join(","));
} else {
this.$emit("change", value, name, sub_cate ? sub_cate : "");
}
},
},
};
</script>
<template>
<div class="bg-filtrate">
<span class="bg-filtrate-text">{{ name }}</span>
<ul class="bg-filtrate-list">
<li
v-for="(item, index) in fullOptions"
:class="{ current: selection.indexOf(item.value) > -1 }"
:key="'li_' + index"
@click="selectAction(item)"
>
{{ item.name }}
</li>
</ul>
</div>
</template>
<script>
export default {
name: "BgFiltrate",
model: {
prop: "value",
event: "change",
},
props: {
value: {
type: [Number, String],
default: "",
},
name: {
type: String,
default: "",
},
options: {
type: Array,
default: () => [],
},
optionName: {
type: String,
default: "name",
},
optionValue: {
type: String,
default: "value",
},
multiple: {
type: Boolean,
default: false,
},
},
computed: {
fullOptions() {
return [
{
name: "全部",
value: "",
},
...this.options.map((item) => {
return {
name: item[this.optionName],
value: item[this.optionValue] + "",
};
}),
];
},
selection() {
let value = this.value + "";
return value.split(",");
},
},
methods: {
selectAction({ value, name }) {
if (value && this.multiple) {
let selection = [...this.selection].filter((v) => v !== "");
let index = selection.findIndex((v) => v === value);
if (index > -1) {
selection.splice(index, 1);
} else {
selection.push(value);
}
this.$emit("change", selection.join(","));
} else {
this.$emit("change", value, name);
}
},
},
};
</script>
\ No newline at end of file
<template>
<svg class="bg-icon" aria-hidden="true">
<use :xlink:href="icon"></use>
</svg>
</template>
<script>
import "https://lf1-cdn-tos.bytegoofy.com/obj/iconpark/svg_19654_220.40af3ff1115b250314027a7e199e042a.js";
export default {
props: {
icon: {
type: String,
default: "",
},
},
};
</script>
\ No newline at end of file
<template>
<div class="bg-info">
<ul>
<li
v-for="(item, index) in data"
:key="'li_' + index"
:style="{
width: item.full ? `100%` : `calc(100% / ${col})`,
}"
>
<span :style="{ width: item.nameWidth ? item.nameWidth + 'px' : '50%'}">
{{ item.name }}
</span>
<span :style="{ width: item.nameWidth ? `calc( 100% - ${item.nameWidth + 'px'})` : '50%'}">
<span
style="display: inline-block;width: 100%;white-space: normal;word-break: break-all"
:style="{
width: item.copy ? 'calc(100% - 36px)' : item.download || item.password ? 'calc(100% - 22px)': '100%',
color: item.download ? '#3759be' : '#404a62'
}"
>{{ item.value }}</span>
<a
class="copy-btn"
@click="copyText(item.value, $event)"
v-if="item.copy"
>
复制
</a>
<bg-icon
class="copy-btn"
style="font-size: 14px;cursor: pointer;"
icon="#bg-ic-download"
v-if="item.download"
@click="download(item.url)"
></bg-icon>
<bg-icon
class="copy-btn"
style="font-size: 14px;cursor: pointer;"
:icon="show ? '#bg-ic-eye-close' : '#bg-ic-eye'"
v-if="item.password"
@click="changeView(item)"
></bg-icon>
</span>
</li>
</ul>
</div>
</template>
<script>
import Clipboard from "clipboard";
export default {
name: "BgInfo",
props: {
data: {
type: Array,
default: () => [],
},
col: {
type: Number,
default: 2,
},
},
data() {
return {
show: false
}
},
methods: {
clipboardSuccess() {
this.$message({
type: "success",
message: "复制成功",
duration: 1500,
});
},
clipboardError() {
this.$message({
message: "浏览器不支持自动复制",
type: "warning",
});
},
copyText(text, e) {
const clipboard = new Clipboard(e.target, {
text: () => text,
});
clipboard.on("success", () => {
this.clipboardSuccess();
// 释放内存
clipboard.destroy();
});
clipboard.on("error", () => {
// 不支持复制
this.clipboardError();
// 释放内存
clipboard.destroy();
});
// 解决第一次点击不生效的问题,如果没有,第一次点击会不生效
clipboard.onClick(e);
},
download(url) {
const a = document.createElement("a"); // 创建a标签
a.setAttribute("download", ""); // download属性
a.setAttribute("href", url); // href链接
a.click(); // 自执行点击事件
},
changeView(item) {
if (!this.show) {
item.value = item.realValue
}else {
item.value = "***************"
}
this.show = !this.show
}
},
};
</script>
<template>
<div class="inner-container" :style="{height:height[0]+'px',fontSize:height[2]+'px'}">
<div :style="{height:height[1]+'px',lineHeight:height[1]-2+'px'}" :class="{'now-inner':nowIndex==index}" @click="changeInner(index)" v-for="(item,index) in data" :key="'inner'+index">{{item}}</div>
</div>
</template>
<script setup>
import { reactive, ref,onBeforeMount,toRefs } from 'vue'
const props = defineProps({
modelValue:{
type: [String,Number],
default: 0,
},
data: {
type: Array,
default: [],
},
default:{
type: [String,Number],
default: 0
},
height:{
type:Array,
default:[36,32,16]
}
})
const emit = defineEmits(['update:modelValue','change'])
const nowIndex = ref('')
const changeInner = (val)=>{
nowIndex.value = val
emit('update:modelValue',val)
emit('change',val)
}
onBeforeMount(()=>{
nowIndex.value = props.default
})
</script>
<style scoped>
.inner-container{
height: 36px;
background-color: #edeef0;
border-radius: 4px;
padding: 2px;
display: inline-block;
overflow: hidden;
}
.inner-container div{
height: 32px;
line-height: 30px;
border-radius: 4px;
padding:0 15px;
float: left;
color: #404a62;
cursor: pointer;
}
.inner-container .now-inner{
background-color: #2b4695;
color: #ffffff;
}
</style>
<template>
<div class="bg-layout-card" :style="style">
<div class="card-title">
<h3 class="title-text text-clip">{{ title }}</h3>
<ul class="filter-list" v-if="btns.length > 0">
<li
v-for="(item, index) in btns"
:key="item"
:class="{ current: index === btn }"
@click="$emit('update:btn', index)"
>
{{ item }}
</li>
</ul>
<div
class="download-btn"
@click="downloadAction"
v-loading="downloading"
element-loading-spinner="el-icon-loading"
v-if="download"
>
<img src="./imgs/btn_daochu.png" />
</div>
</div>
<div class="card-content" ref="content">
<slot />
</div>
</div>
</template>
<script>
import html2canvas from "html2canvas";
export default {
name: "BgLayoutCard",
props: {
title: {
type: String,
default: "",
},
width: {
type: String,
default: "25%",
},
height: {
type: String,
default: "278px",
},
btn: {
type: Number,
default: 0,
},
btns: {
type: Array,
default: () => [],
},
download: {
type: Boolean,
default: false,
},
},
computed: {
style() {
return {
width: `calc(${this.width} - 20px)`,
height: this.height,
};
},
},
data() {
return {
downloading: false,
};
},
methods: {
getScrollTop() {
let scrollTop = 0;
if (document.documentElement && document.documentElement.scrollTop) {
scrollTop = document.documentElement.scrollTop;
} else if (document.body) {
scrollTop = document.body.scrollTop;
}
return scrollTop;
},
downloadAction() {
if (this.downloading) {
return;
}
let content = this.$refs.content;
let { top, left } = content.getBoundingClientRect();
let scrollTop = this.getScrollTop();
this.downloading = true;
html2canvas(content, { x: left, y: top + scrollTop }).then((canvas) => {
let imgUrl = canvas.toDataURL("image/png");
let a = document.createElement("a"); // 生成一个a元素
let event = new MouseEvent("click"); // 创建一个单击事件
this.$nextTick(() => {
a.download = this.title; // 设置图片名称
a.href = imgUrl; // 将生成的URL设置为a.href属性
a.dispatchEvent(event); // 触发a的单击事件
this.downloading = false;
});
});
},
},
};
</script>
<template>
<div class="bg-list bg-form">
<!-- 面包屑 -->
<div class="bg-breadcrumb">
<slot name="breadcrumb" />
</div>
<!-- 头部 -->
<div class="bg-list-header" v-if="$slots.header">
<slot name="header" />
</div>
<!-- 筛选条件 -->
<div class="bg-list-filter" v-if="$slots.filters">
<div class="fiter-header" v-if="!inlineFilters">
<span v-if="noMoreFilters">
<!-- -->
</span>
<template v-else>
<el-button @click="$emit('update:visible', !visible)" v-if="visible">
<span>收起</span>
<i class="el-icon-caret-top el-icon--right"></i>
</el-button>
<el-button @click="$emit('update:visible', !visible)" v-else>
<span>更多选项</span>
<i class="el-icon-caret-bottom el-icon--right"></i>
</el-button>
</template>
<div class="filter-header-right">
<slot name="filter" />
</div>
</div>
<div
class="filter-content"
:class="{ 'inline-filters': inlineFilters, 'show-more': visible }"
>
<div class="filter-list">
<slot name="filters" />
</div>
<div class="filter-action" v-if="inlineFilters">
<div class="filters-right" v-if="$slots['filters-right']">
<slot name="filters-right" />
</div>
<el-button type="primary2" @click="$emit('search-action')">
查询
</el-button>
<el-button type="default2" @click="$emit('search-reset')">
重置
</el-button>
</div>
</div>
</div>
<!-- 其他信息 -->
<div class="bg-list-top" v-if="$slots.top">
<slot name="top" />
</div>
<!-- 列表 -->
<div class="bg-list-main" :class="{ 'has-action': !!$slots.action }">
<div class="main-action" v-if="$slots.action">
<slot name="action" />
</div>
<div class="main-table">
<slot name="table" />
</div>
<div class="main-pagination">
<slot name="pagination" />
</div>
</div>
<slot />
</div>
</template>
<script>
export default {
name: "BgList",
props: {
visible: {
type: Boolean,
default: false,
},
noMoreFilters: {
type: Boolean,
default: false,
},
inlineFilters: {
type: Boolean,
default: false,
},
},
};
</script>
<template>
<ul class="nav-list" v-if="list&&list.length">
<li v-for="(item, index) in list" v-show="item.menuType!==2" :key="'nav_' + index">
<template v-if="item.children && item.children.length&&item.menuType==0">
<div
class="nav-item nav-more text-clip"
:class="{ current: isCurrent([item.path]) }"
@click="showMoreAction(index)"
>
<span :style="{ paddingLeft: `${deep*2}em` }">
<!-- <img v-if="item.icon" :src="item.icon" alt=""> -->
<bg-icon v-if="item.icon" style="color:#7c8292;" :icon="'#'+item.icon"></bg-icon>
{{ item.menuName }}
<bg-icon v-show="showMore[index] !== false" style="font-size:8px;position: absolute;right: 10px;top: 20px;" icon="#bg-ic-arrow-up"></bg-icon>
<bg-icon v-show="showMore[index] == false" style="font-size:8px;position: absolute;right: 10px;top: 20px;" icon="#bg-ic-arrow-down"></bg-icon>
</span>
&ensp;
</div>
<transition name="slideOutUp">
<NavList
:list="item.children"
:deep="deep + 1"
:highlight-parent-rule="highlightParentRule"
v-if="showMore[index] !== false"
/>
</transition>
</template>
<template v-else>
<div
class="nav-item text-clip"
:class="{current:isCurrent(item.children&&item.children.length?[...getChildrenPath(item.children),item.path]:[item.path])}"
@click="$router.push(item.path)"
>
<span :style="{ paddingLeft: item.icon ? `${deep*2 - 1.37}em` :`${deep*2}em` }">
<bg-icon v-if="item.icon" style="color:#7c8292;" :icon="'#'+item.icon"></bg-icon>
{{ item.menuName }}
</span>
</div>
</template>
</li>
</ul>
</template>
<script>
export default {
name: "NavList",
props: {
list: {
type: Array,
required: true,
}, // 导航列表 [ { name: "xxx", path: "xxx" } ]
deep: {
type: Number,
default: 0,
},
highlightParentRule: {
type: Function,
},
},
data() {
return {
showMore: {},
};
},
methods: {
showMoreAction(index) {
let flag = this.showMore[index];
if (flag === undefined) {
flag = true;
}
this.showMore[index] = !flag
},
getChildrenPath(arr,temp=[]){
arr.forEach(e => {
temp.push(e.path)
if(e.children&&e.children.length){
this.getChildrenPath(e.children,temp)
}
});
return temp
},
isCurrent(path) {
return (
(this.highlightParentRule && this.highlightParentRule(path)) || false
);
},
},
};
</script>
<template>
<div class="bg-nav" :style="{ width: width }">
<div class="bg-nav-title" v-if="title">
<h3 class="text-clip">{{ title }}</h3>
</div>
<div class="bg-nav-list bg-scroll">
<NavList :list="list" :highlight-parent-rule="highlightParentRule" />
</div>
</div>
</template>
<script>
import NavList from "./bg-nav-list.vue";
export default {
name: "BgNav",
components: {
NavList,
},
props: {
title: {
type: String,
default: "",
},
width: {
type: String,
default: "184px",
}, // 宽度
list: {
type: Array,
required: true,
}, // 导航列表 [ { name: "xxx", path: "xxx" } ]
highlightParentRule: {
type: Function,
},
},
};
</script>
<template>
<div class="bg-pagination">
<el-pagination
:currentPage="page"
:page-size="size"
:page-sizes="pageSizes"
:layout="layout"
:total="total"
@size-change="changeSize"
@current-change="changePage"
:background="background"
:disabled="disabled"
/>
</div>
</template>
<script>
export default {
name: "pagination",
props: {
page: {
type: Number,
default: 1
},
size: {
type: Number,
default: 10,
},
pageSizes: {
type: Array,
default: [10,50,100]
},
total: {
type: Number,
default: 0
},
layout: {
type: String,
default: "total, sizes, prev, pager, next, jumper"
},
background: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
}
},
setup(props, context) {
const changePage = (val) => {
context.emit("change-page",val)
}
const changeSize = (val) => {
context.emit("change-size",val)
}
return {
changePage,
changeSize
}
},
}
</script>
\ No newline at end of file
<template>
<div style="border: 1px solid #ccc;z-index: 100;">
<Toolbar
style="border-bottom: 1px solid #ccc"
:editor="editorRef"
:defaultConfig="toolbarConfig"
:mode="mode"
/>
<Editor
style="height: 500px; overflow-y: hidden;"
v-model="valueHtml"
@onChange="handleChange"
@onBlur="handleBlur"
:defaultConfig="editorConfig"
:mode="mode"
@onCreated="handleCreated"
/>
</div>
</template>
<script>
import '@wangeditor/editor/dist/css/style.css' // 引入 css
import { onBeforeUnmount, ref, shallowRef, onMounted, reactive, toRefs } from 'vue'
import { Editor, Toolbar } from '@wangeditor/editor-for-vue'
import {
useFormItem,
} from 'element-plus'
export default {
components: { Editor, Toolbar },
props: {
modelValue: {
type: String,
default: '',
},
disabled:{
type:Boolean,
default:false
}
},
emits: ['update:modelValue','change','blur'],
watch:{
modelValue(n,o){
this.valueHtml = n
}
},
setup(props,{ emit }) {
//引入form-item
const { formItem } = useFormItem()
// 编辑器实例,必须用 shallowRef
const editorRef = shallowRef()
// 内容 HTML
const valueHtml = ref('')
// change次数
let changeNum = ref(0)
// 模拟 ajax 异步获取内容
onMounted(() => {
setTimeout(()=>{
valueHtml.value = props.modelValue
// console.log(valueHtml.value);
})
})
const toolbarConfig = {}
const editorConfig = {
placeholder: '请输入内容...',
MENU_CONF: {
uploadImage: {
server: '/apaas/common/image/upload', // 服务器地址
fieldName: "file", // 上传的文件的字段名称
meta: {
directory: 'image',
}, // 上传图片必须携带的参数
maxFileSize: 2 * 1024 * 1024, // 图片最大2M
timeout: 3 * 60 * 1000, // 超时时间3分钟
allowedFileTypes: ['image/jpg','image/png','image/gif','image/jpeg',],
customInsert(res, insertFn) {
// res 即服务端的返回结果
// 从 res 中找到 url alt href ,然后插图图片 url是必须,href和alt可以为""
let url = res.data
let alt = ""
let href = res.data
insertFn(url, alt, href)
},
onBeforeUpload(file) {
// 可以 return
// 1. return file 或者 new 一个 file ,接下来将上传
// 2. return false ,不上传这个 file
let allowedType = ["jpg","png","jpeg","gif","bmp","tiff"] // 常见的图片格式
let data = Object.values(file)[0]
if (allowedType.indexOf(data.extension) == -1) {
alert(`图片验证未通过,【${data.name}】不是图片`)
return false
}else {
return file
}
},
onError(file, err, res) {
console.log(`${file.name} 上传出错`, err, res)
},
}
}
}
// watch(
// props,modelValue,
// (n,o) => {
// }
// )
// 组件销毁时,也及时销毁编辑器
onBeforeUnmount(() => {
const editor = editorRef.value
if (editor == null) return
editor.destroy()
})
const handleCreated = (editor) => {
editorRef.value = editor // 记录 editor 实例,重要!
if(props.disabled){
editor.disable()
}
}
const handleChange = ()=>{
//初始化会默认赋值<p><br></p>
//会对原有数据造成影响,return掉
changeNum.value++
if(changeNum.value==1){
return
}
emit("update:modelValue", valueHtml.value);
// formItem?.validate?.('change').catch((err) => debugWarn(err))
}
const handleBlur = ()=>{
formItem?.validate?.('blur').catch((err) => console.warn(err))
}
return {
editorRef,
valueHtml,
mode: 'default', // 或 'simple'
toolbarConfig,
editorConfig,
handleCreated,
handleChange,
handleBlur,
};
}
}
</script>
<style>
.w-e-toolbar p, .w-e-text-container p, .w-e-menu-panel p {
font-size: 14px !important;
color: #202531!important;
}
.w-e-text-container{
word-break: break-all;
}
</style>
\ No newline at end of file
<template>
<div class="bg-sort">
<a
v-for="(item, index) in types"
:key="'name_' + index"
:class="{ current: item.value === value }"
@click="selectAction(item)"
>
{{ item.name }}
</a>
</div>
</template>
<script>
export default {
name: "BgSort",
model: {
prop: "value",
event: "change",
},
props: {
value: {
type: [Number, String],
default: "",
},
types: {
type: Array,
default: () => [
{
name: "综合排序",
value: 0,
},
{
name: "最近更新",
value: 1,
},
{
name: "最高人气",
value: 2,
},
{
name: "最好评价",
value: 3,
},
],
},
},
methods: {
selectAction({ value }) {
this.$emit("change", value);
},
},
};
</script>
\ No newline at end of file
<template>
<div class="bg-step" v-show="active === step">
<div class="step-content">
<slot />
</div>
<div class="step-action bg-form">
<slot name="action" />
</div>
</div>
</template>
<script>
export default {
name: "BgStep",
inject: {
getActive: {
type: Function,
default: () => {
return this.$parent.getActive;
},
},
},
props: {
title: {
type: String,
default: "",
},
step: {
type: Number,
required: true,
},
icon: {
required: true,
},
},
computed: {
active() {
return this.getActive();
},
},
};
</script>
<template>
<div class="bg-steps">
<div class="bg-steps-container">
<div class="steps-nav">
<ul>
<template v-for="(item, index) in calcSteps()" :key="'tab_' + index">
<li
class="step-line"
:class="{
done: active > index,
current: active === index,
}"
v-if="index > 0"
></li>
<li
class="step-item"
:class="{
done: active > index,
current: active === index,
}"
>
<div class="step-icon">
<!-- -->
</div>
<div class="step-hightlight-icon">
<img :src="item.icon" />
</div>
<div class="step-title">
<p>
{{ item.title || "完成" }}
</p>
<p v-if="item.title">
<template v-if="active > index">
已完成
</template>
<template v-else-if="active === index">
进行中
</template>
<template v-else>
未进行
</template>
</p>
</div>
</li>
<li
class="step-line"
:class="{
done: active > index,
current: active === index,
}"
v-if="index < calcSteps().length - 1"
></li>
</template>
</ul>
</div>
<div class="steps-content">
<slot />
</div>
</div>
</div>
</template>
<script>
export default {
name: "BgDetail",
provide() {
return {
getActive: () => {
return this.active;
},
};
},
props: {
active: {
type: Number,
default: 0,
},
},
data() {
return {
showFixedBars: false,
scrollCallback: null,
};
},
methods: {
calcSteps() {
let stepSlots = [];
if (this.$slots.default) {
stepSlots = this.$slots.default
.filter(
(vnode) =>
vnode.tag &&
vnode.componentOptions &&
vnode.componentOptions.Ctor.options.name === "BgStep"
)
.map((vnode) => {
return vnode.componentOptions.propsData;
});
}
return stepSlots;
},
},
};
</script>
<template>
<div
ref="bg_switch"
class="bg-switch"
:class="{ disabled: disabled }"
:style="now_style"
@click="switch_data"
>
<span class="label" :style="now_label_style">
{{ labels[now_index] }}
</span>
<span class="circle" :style="now_circle_style" ref="circle">
<!-- circle -->
</span>
</div>
</template>
<script>
export default {
name: "BgSwitch",
props: {
modelValue: {
type: [Boolean, Number, String],
default: 0,
},
labels: {
type: Array,
default: () => ["停用", "启用"],
},
values: {
type: Array,
default: () => [0, 1],
},
colors: {
type: Array,
default: () => ["#c1c7d7", "#275a9d"],
},
disabled: {
type: Boolean,
default: false,
},
},
emits: ['update:modelValue'],
data() {
return {
gap: 0,
box_height: 0,
circle_height: 0,
};
},
computed: {
now_index() {
if (this.values[0] == this.modelValue) {
return 0;
} else {
return 1;
}
},
now_style() {
return {
color: this.colors[this.now_index],
borderColor: this.colors[this.now_index],
};
},
now_label_style() {
return this.now_index == 0
? { left: this.circle_height + this.gap + 5 + "px" }
: { left: "10px" };
},
now_circle_style() {
return this.now_index == 0
? {
left: this.gap + "px",
backgroundColor: this.colors[this.now_index],
}
: {
right: this.gap + "px",
backgroundColor: this.colors[this.now_index],
};
},
},
methods: {
switch_data() {
if (this.disabled) {
return;
}
if (this.values[0] == this.modelValue) {
this.$emit("update:modelValue", this.values[1]);
} else {
this.$emit("update:modelValue", this.values[0]);
}
},
},
mounted() {
this.box_height = this.$refs.bg_switch.offsetHeight;
this.circle_height = this.$refs.circle.offsetHeight;
this.gap = (this.box_height - this.circle_height - 4) / 2;
},
};
</script>
<template>
<div class="bg-tab" v-show="showTab">
<div class="tab-title" v-if="!isTabs">
<h3>{{ label }}</h3>
</div>
<div class="tab-content">
<slot />
</div>
</div>
</template>
<script>
export default {
name: "BgTab",
inject: {
getActiveName: {
type: Function,
default: () => {
return this.$parent.activeName;
},
},
getIsTabs: {
type: Function,
default: () => {
return false;
},
},
},
props: {
label: {
type: String,
required: true,
},
name: {
type: String,
required: true,
},
},
computed: {
activeName() {
return this.getActiveName();
},
isTabs() {
return this.getIsTabs();
},
showTab() {
if (this.isTabs) {
if (this.activeName === this.name) {
return true;
} else {
return false;
}
} else {
return true;
}
},
},
};
</script>
<template>
<a class="bg-table-btn" :class="{ disabled: disabled }" @click="clickAction">
<slot />
</a>
</template>
<script>
export default {
name: "BgTableBtn",
props: {
disabled: {
type: Boolean,
default: false,
},
click: {
type: Function,
default: () => null,
},
},
emits: ["click"],
methods: {
clickAction() {
if (this.disabled) {
return;
}
this.$emit("click");
this.click && this.click();
},
},
};
</script>
<template>
<div class="bg-table-btns-more" :style="style" @click.stop>
<bg-table-btn
v-for="(item, index) in operations"
:key="index"
:disabled="isDisabled(item)"
@click="clickAction(item)"
>
{{ getName(item) }}
</bg-table-btn>
</div>
</template>
<script>
export default {
name: "BgTableBtnsMore",
props: {
operations: {
type: Array,
default: () => [],
},
},
data() {
return {
style: {},
};
},
methods: {
isDisabled({ disabled }) {
if (typeof disabled === "function") {
return disabled();
} else if (typeof disabled === "boolean") {
return disabled;
} else if (typeof disabled === "undefined") {
return false;
} else {
return !!disabled;
}
},
clickAction({ callback }) {
this.$parent.hideBox()
typeof callback === "function" && callback();
},
getName({ name }) {
if (typeof name === "function") {
return name();
} else {
return name;
}
},
upStyle() {
let { top, right } = this.$parent.$el.getBoundingClientRect();
let { width } = window.document.body.getBoundingClientRect();
this.style = {
position: "fixed",
top: `${top + 16}px`,
right: `${width - right - 16}px`,
};
},
},
mounted() {
document.body.append(this.$el);
this.upStyle();
},
beforeDestroy() {
this.$el && this.$el.remove();
},
};
</script>
<template>
<div class="bg-table-btns">
<bg-table-btn
class="can_click_text"
v-for="(item, index) in curOperations"
:key="index"
:disabled="isDisabled(item)"
@click="clickAction(item)"
>
{{ getName(item) }}
</bg-table-btn>
<a
class="bg-table-btn"
@mouseenter="showMOreBtns"
@mouseleave="hideMoreBtns"
v-if="otherOperations.length > 0"
>
<!-- <i class="el-icon-more" /> -->
<bg-icon style="font-size: 12px; color: #2b4695;" icon="#bg-ic-s-more" />
<bg-table-btns-more
:operations="otherOperations"
@mouseenter="showMOreBtns"
@mouseleave="hideMoreBtns"
v-if="showMore"
/>
</a>
</div>
</template>
<script>
import BgTableBtnsMore from "./bg-table-btns-more.vue";
export default {
name: "BgTableBtns",
components: { BgTableBtnsMore },
props: {
operations: {
type: Array,
default: () => [],
},
},
data() {
return {
showMore: false,
timer: null,
};
},
computed: {
curOperations() {
return this.operations.slice(0, this.operations.length > 3 ? 2 : 3);
},
otherOperations() {
return this.operations.slice(2, this.operations.length);
},
},
methods: {
hideBox() {
this.showMore = false
},
isDisabled({ disabled }) {
if (typeof disabled === "function") {
return disabled();
} else if (typeof disabled === "boolean") {
return disabled;
} else if (typeof disabled === "undefined") {
return false;
} else {
return !!disabled;
}
},
clickAction({ callback }) {
typeof callback === "function" && callback();
},
getName({ name }) {
if (typeof name === "function") {
return name();
} else {
return name;
}
},
showMOreBtns() {
if (this.timer) clearTimeout(this.timer);
this.showMore = true;
},
hideMoreBtns() {
if (this.timer) clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.showMore = false;
}, 50);
},
},
};
</script>
<template>
<el-table
ref="table"
class="bg-table bg-table-pro"
:class="{ 'bg-table-tree': !!rowKey }"
v-bind="$attrs"
:data="data"
:row-key="rowKey"
:tree-props="treeProps"
@selection-change="selectionChange"
:default-expand-all="defaultExpandAll"
tooltip-effect="light"
>
<el-table-column width="60" v-if="showIndex">
<template v-slot:header>
<p style="width: 100%; text-align: center">序号</p>
</template>
<template v-slot:default="{ $index }">
<p style="width: 100%; text-align: center">{{ $index + 1 }}</p>
</template>
</el-table-column>
<el-table-column
type="selection"
:selectable="selectable"
width="60"
align="center"
v-if="showSelctColumn"
/>
<el-table-column
v-for="(header, index) in headers"
:key="`col_${index}`"
:width="header.width"
:label="header.label"
:align="header.align"
:show-overflow-tooltip="!$slots[header.prop]"
:fixed="header.fixed"
>
<template v-slot:header>
<template v-if="$slots[`header-${header.prop}`]">
<slot :name="`header-${header.prop}`" />
</template>
<template v-else>
<p class="text-clip">
<span v-if="header.required" style="color: #d75138">*</span>
{{ header.label }}
</p>
</template>
</template>
<template v-slot:default="{ row, $index }">
<template v-if="$slots[header.prop]">
<slot :name="header.prop" :row="row" :index="$index" />
</template>
<template v-else-if="$slots[`text-${header.prop}`]">
<p class="text-clip">
<slot :name="`text-${header.prop}`" :row="row" :index="$index" />
</p>
</template>
<template v-else>
<p class="text-clip">{{ row[header.prop] }}</p>
</template>
</template>
</el-table-column>
</el-table>
</template>
<script>
export default {
name: "BgTablePro",
props: {
headers: {
type: Array,
require: true,
},
data: {
type: Array,
},
rowKey: {
type: String,
},
treeProps: {
type: Object,
},
showIndex: {
type: Boolean,
default: false,
},
selectable: {
type: Function,
},
defaultExpandAll: {
type: Boolean,
default: false,
},
},
data() {
return {
allSelection: [], // 所有页面上的选中的数据
};
},
computed: {
showSelctColumn() {
return (
this.$attrs &&
(this.$attrs["selection-change"] || this.$attrs["select"])
);
}, // 是否显示选中列
addSelectEvent() {
return this.$attrs && this.$attrs["select"];
}, // 是否监听select事件 select事件会记录所有页面上的选中的数据
},
watch: {
data: {
handler() {
this.recoverSelection();
},
deep: true,
},
},
methods: {
recoverSelection() {
let selectionIds = this.allSelection.map((v) => v[this.rowKey]);
this.data.forEach((v) => {
if (selectionIds.indexOf(v[this.rowKey]) > -1) {
this.$nextTick(() => {
console.log({ ...v });
this.$refs.table.toggleRowSelection(v, true);
});
}
});
}, // 恢复选中
selectionChange(selection) {
if (!this.addSelectEvent) return; // 如果用户未监听select事件,则不执行
if (!this.rowKey) throw Error("监听select事件时,row-key必须传入!");
this.upAllSelection(selection);
this.$emit("select", this.allSelection);
}, // select事件
upAllSelection(selection) {
let rowIds = this.data.map((v) => v[this.rowKey]);
let allSelection = [...this.allSelection];
let selectionIds = allSelection.map((v) => v[this.rowKey]);
// 首先把当前页的选中全部移除
selectionIds = selectionIds.filter((v) => rowIds.indexOf(v) === -1);
// allSelection仅保留selectionIds存在的
allSelection = allSelection.filter(
(v) => selectionIds.indexOf(v[this.rowKey]) > -1
);
// 然后再加入当前页的选中
allSelection.push(...selection);
this.allSelection = allSelection;
}, // 更新当前全部被选中的数据
clearSelection() {
this.$refs.table.clearSelection();
this.allSelection = [];
}, // 清空选中
getRowInfo(row, key) {
let currentIndex = -1;
let parentRows = null;
let propPath = "";
let childrenKey = this.treeProps.children;
let recursionItems = (items, prop) => {
for (let i = 0; i < items.length; i++) {
let item = items[i];
propPath += `${i}.`;
if (item[prop] === row[prop]) {
currentIndex = i;
parentRows = items;
break;
} else if (item[childrenKey] && item[childrenKey].length > 0) {
propPath += `${childrenKey}.`;
recursionItems(item[childrenKey], prop);
}
}
};
recursionItems(this.data, key, "");
return {
index: currentIndex,
rows: parentRows,
$_prop_path: propPath,
};
},
},
};
</script>
\ No newline at end of file
<template>
<el-table
ref="table"
class="bg-table"
:data="rows"
@selection-change="selectAction"
@select="selectActionRow"
@select-all="selectActionAll"
:stripe="stripe"
:row-class-name="stripe ? tableRowClassName : ''"
tooltip-effect="light"
>
<template v-slot:empty>
<div class="empty_container">
<img src="../assets/imgs/img-no-data.png" alt="">
<div class="text">
暂无数据
</div>
</div>
</template>
<el-table-column
v-if="paddingLeft > 10"
:width="paddingLeft - 10"
></el-table-column>
<el-table-column
type="selection"
:selectable="selectable"
width="38"
align="center"
v-if="select"
>
<!-- checkbox -->
</el-table-column>
<el-table-column
v-if="isIndex"
type="index"
:label="indexLabel"
width="54"
align="left"
>
<!-- 序号 -->
</el-table-column>
<el-table-column
v-for="(header, index) in headers"
:width="header.width"
:min-width="header.minWidth"
:align="header.align"
:key="'col_' + index"
:fixed="header.fixed"
show-overflow-tooltip
>
<template v-slot:header>
<template v-if="$slots[`header-${header.prop}`]">
<slot :name="`header-${header.prop}`" />
</template>
<p class="text-clip" v-else>{{ header.label }}</p>
</template>
<template v-slot:default="{ row }">
<slot v-if="$slots[header.prop]" :name="header.prop" :row="row" />
<template v-else>
<!-- {{ row[header.prop] | ellipsis(header.max) }} -->
{{ row[header.prop] }}
</template>
</template>
</el-table-column>
</el-table>
</template>
<script>
import { selectTableMixin } from './hook/mixin-select-table'
export default {
name: "BgTable",
setup() {
const {
nowSelectData,
allSelectData,
selectData,
initSelectTableData,
runPage,
dealSelectData
} = selectTableMixin()
return {
nowSelectData,
allSelectData,
selectData,
initSelectTableData,
runPage,
dealSelectData
}
},
props: {
headers: {
type: Array,
require: true,
},
rows: {
type: Array,
default: () => [],
},
select: {
type: Boolean,
default: false,
},
selectable: {
type: Function,
},
isIndex: {
type: Boolean,
default: false
},
indexLabel: {
type: String,
default: "序号"
},
stripe: {
type: Boolean,
default: false
},
paddingLeft: {
type: [Number, String],
default: () => 33,
},
},
/* filters: {
ellipsis(value, max) {
if (!value) {
return "";
} else if (max && value.length > max) {
return value.slice(0, max - 1) + "...";
} else {
return value;
}
},
}, */
watch:{
rows(n,o){
if(n.length&&this.select){
this.runPage()
this.initSelectTableData(n).then((selectData)=>{
if(selectData.length){
setTimeout(()=>{
this.toggleRowArrSelection(selectData)
})
}
})
}
}
},
created(){
console.log(this.$attrs)
},
methods: {
toggleRowSelection(row, flag = true) {
this.$refs.table.toggleRowSelection(row, flag);
},
selectAction(selection) {
this.$emit("selectAc", {allLength:Object.keys(this.allSelectData).length+this.nowSelectData.length,selection});
},
clearSelection() {
this.$refs.table.clearSelection();
this.$emit("select", {allLength:Object.keys(this.allSelectData).length+this.nowSelectData.length,selection:[]});
},
setSelectedRow(row) {
this.$refs.table.toggleRowSelection(row);
},
toggleRowArrSelection(arr, flag = true){
arr.forEach(e => {
this.$refs.table.toggleRowSelection(e, flag);
});
},
selectActionRow(selection,row){
this.selectData(selection)
this.$emit("select", {allLength:Object.keys(this.allSelectData).length+this.nowSelectData.length,selection});
},
selectActionAll(selection){
this.selectData(selection)
this.$emit("select", {allLength:Object.keys(this.allSelectData).length+this.nowSelectData.length,selection});
},
clearTable(){//清除选中数据,在页面状态更新时使用
this.allSelectData={}
this.nowSelectData=[]
this.clearSelection();
},
tableRowClassName({ row, rowIndex }) {
if (rowIndex % 2 == 0) {
return "white-row";
} else {
return "stripe-row";
}
},
},
};
</script>
<template>
<div class="bg-tabs nice-tabs">
<div class="bg-tabs-nav">
<ul>
<li
v-for="(item, index) in calcTabs()"
:key="'tab_' + index"
:class="{
current: modelValue === item.name,
}"
@click="changeActiveName(item, index)"
>
{{ item.label }}
</li>
<li>
<!-- empty -->
</li>
</ul>
</div>
<div class="bg-tabs-content">
<slot />
</div>
<div class="bg-tabs-action" v-if="$slots.action">
<slot name="action" />
</div>
</div>
</template>
<script>
export default {
name: "BgTabs",
provide() {
return {
getActiveName: () => {
return this.modelValue;
},
getIsTabs: () => {
return true;
},
};
},
props: {
modelValue: {
type: String,
default: '',
},
},
emits: ['update:modelValue'],
data() {
return {
isTabs: true,
};
},
methods: {
calcTabs() {
let tabSlots = [];
if (this.$slots.default) {
tabSlots = this.$slots.default
.filter(
(vnode) =>
vnode.tag &&
vnode.componentOptions &&
vnode.componentOptions.Ctor.options.name === "BgTab"
)
.map((vnode) => vnode.componentOptions.propsData);
}
return tabSlots;
},
changeActiveName({ name }) {
this.$emit("update:modelValue", name);
},
},
};
</script>
<template>
<div class="bg-tags">
<ul>
<li v-for="(tag, index) in tags" :key="'tag_' + index" class="tag-item">
<span>
{{ tag }}
</span>
<a @click="deleteTag(index)" v-if="!disabled">
<i class="el-icon-close"></i>
</a>
</li>
<li v-if="!disabled">
<el-button
type="primary2"
size="mini"
@click="showInput = true"
v-if="!showInput"
>
新增
</el-button>
<el-input v-model.trim="newTag" @blur="addTag" v-else />
</li>
</ul>
</div>
</template>
<script>
export default {
name: "BgTags",
model: {
prop: "value",
event: "change",
},
props: {
value: {
type: String,
default: "",
},
disabled: {
type: Boolean,
default: false,
},
},
data() {
return {
newTag: "",
showInput: false,
};
},
computed: {
tags() {
return (this.value && this.value.split(",")) || [];
},
},
methods: {
deleteTag(index) {
let tags = [...this.tags];
tags.splice(index, 1);
this.$emit("change", tags.join(","));
},
addTag() {
let tags = [...this.tags];
if (this.newTag) {
tags.push(this.newTag);
}
this.$emit("change", tags.join(","));
this.newTag = "";
this.showInput = false;
},
},
};
</script>
<template>
<el-upload
ref="upload"
class="bg-upload"
v-bind="$attrs"
:file-list="fileList"
:action="action"
:data="{
directory: 'image',
uniqueCode: false
}"
:auto-upload="autoUpload"
:list-type="listType"
:limit="limit"
:accept="acceptTypes"
:before-upload="handleBeforeUpload"
:on-success="handleSuccess"
:on-remove="handleRemove"
:on-preview="handlePreview"
:multiple="multiple"
>
<template v-slot:trigger>
<el-icon v-if="listType === 'picture-card'"><Plus /></el-icon>
<!-- <i class="el-icon-plus" v-if="listType === 'picture-card'"></i> -->
<el-button type="primary" :icon="UploadFilled" v-else>
{{ triggerText }}
</el-button>
</template>
<template v-if="!autoUpload">
<el-button
type="success"
icon="el-icon-position"
@click="submitUpload"
style="margin-left: 16px"
>
上传到服务器
</el-button>
</template>
<template v-slot:tip v-if="showTips && tips">
<div class="el-upload__tip" style="color:#909bb6;">
{{ customTips||tips }}
</div>
</template>
</el-upload>
<el-dialog title="图片预览" v-model="dialogVisible">
<img style="margin:30px 0;width: 100%;" :src="dialogImageUrl" alt="Preview Image" />
</el-dialog>
</template>
<script>
import {
UploadFilled
} from '@element-plus/icons-vue'
export default {
name: "BgUploadImage",
props: {
modelValue:{
type: Array,
default: () => [],
},
action: {
type: String,
default: "/apaas/common/image/upload",
},
autoUpload: {
type: Boolean,
default: true,
},
listType: {
type: String,
default: "text",
},
limit: {
type: Number,
},
multiple: {
type: Boolean,
default: false,
},
accept: {
type: [Array, String],
}, // 接受类型
fileSize: {
type: Number,
}, // 文件大小
fileSizeUnit: {
type: String,
default: "MB",
}, // 文件单位 KB/MB/GB
triggerText: {
type: String,
default: "点击上传",
}, // 按钮文字
showTips: {
type: Boolean,
default: false,
}, // 显示提示
customTips:{
type: String,
default: "",
},// 自定义提示内容
},
emits: ['update:modelValue',"change"],
computed: {
acceptTypes() {
if (Array.isArray(this.accept)) {
return this.accept.join(",");
} else {
return this.accept;
}
},
types() {
return this.acceptTypes.split(",").filter((v) => v !== "");
},
tips() {
let str = "";
if (this.types.length > 0) {
str += `后缀为 ${this.types.join("")} `;
}
if (this.fileSize) {
if (str) str += "";
str += `大小不超过 ${this.fileSize}${this.fileSizeUnit}`;
}
if (str) str += "的文件";
if (this.limit) {
str += `,最多上传 ${this.limit} 个文件`;
}
if (str) {
str = "支持" + str;
}
return str;
},
},
data() {
return {
fileList: [],
UploadFilled,
dialogImageUrl:'',
dialogVisible:false
};
},
watch: {
modelValue() {
let newStr = this.modelValue.map((v) => v.url).join(",");
let oldStr = this.fileList
.map((v) => (v.response && v.response.data) || v.url)
.join(",");
if (newStr !== oldStr) {
this.fileList = [...this.modelValue];
}
this.$nextTick().then(() => {
this.checkLimit(this.modelValue)
});
},
},
methods: {
checkLimit(filelist) {
const limit = this.limit
const uploadDom = this.$refs['upload']
const length = uploadDom.$el.children[0].children.length
if (filelist.length === limit) {
uploadDom.$el.children[0].children[length-1].style.display = 'none'
} else {
uploadDom.$el.children[0].children[length-1]?uploadDom.$el.children[0].children[length-1].style.display = '':''
}
},
handleBeforeUpload(file) {
let units = {
KB: 1024,
MB: 1024 * 1024,
GB: 1024 * 1024 * 1024,
};
let temp = file.name.split(".");
let type = "." + temp[temp.length - 1].toLocaleLowerCase();
let fileTypeIsOk =
this.types.length === 0 || this.types.indexOf(type) > -1;
let fileSizeIsOk =
this.fileSize === 0 ||
this.fileSize === undefined ||
file.size / units[this.fileSizeUnit] <= this.fileSize;
let checked = fileTypeIsOk && fileSizeIsOk;
if (!checked) {
this.$message.error(this.tips);
}
return checked;
},
handleSuccess(response, file, fileList) {
this.updateFileList(fileList);
this.checkLimit(fileList)
},
handleRemove(file, fileList) {
this.updateFileList(fileList);
this.checkLimit(fileList)
},
handlePreview({ name, url }) {
this.dialogImageUrl = url
this.dialogVisible = true
// let a = document.createElement("a"); // 生成一个a元素
// let event = new MouseEvent("click"); // 创建一个单击事件
// a.download = name; // 设置图片名称
// a.href = url; // 将生成的URL设置为a.href属性
// a.dispatchEvent(event); // 触发a的单击事件
},
updateFileList(fileList) {
let values = fileList.map((v) => {
return {
name: v.name,
url: (v.response && v.response.data) || v.url,
};
});
this.fileList = fileList;
console.log(values);
this.$emit("update:modelValue", values);
this.$emit("change",values);
},
submitUpload() {
this.$refs.upload.submit();
},
},
mounted() {
this.fileList = [...this.modelValue];
},
};
</script>
<template>
<div
class="bg-upload"
:class="{ 'is-disabled': actionDisabled, 'is-easy': isEasy }"
>
<el-upload
action="/apaas/common/file/upload"
:data="{
directory: 'file',
uniqueCode: false,
}"
:before-upload="handleBeforeUpload"
:on-exceed="handleExceed"
:on-success="handleSuccess"
:on-preview="handlePreview"
:on-remove="handleRemove"
:file-list="fileList"
:disabled="actionDisabled"
style="max-width: 600px"
drag
multiple
>
<!-- <el-button type="primary">
上传附件
</el-button>
<div class="el-upload__tip" slot="tip">
支持上传{{ fileTypes.join("") }},最大{{ fileMaxSize }}M
</div> -->
<template v-if="isEasy">
<el-button type="primary" size="mini">
<bg-icon
:icon="`#${triggerIcon}`"
v-if="triggerIcon"
style="margin-right: 8px"
/>
{{ triggerText }}
</el-button>
</template>
<div class="trigger-content" v-else>
<div class="trigger-icon">
<img src="./imgs/ic_updata_cloud.png" />
</div>
<p class="trigger-tip" style="text-align: center" v-if="customTips">
<slot></slot>
</p>
<p class="trigger-tip" v-else>
1.最多允许上传{{ limit }}个附件 <br />
2.单个附件最大{{ fileMaxSize }}M; <br />
3.允许上传后缀为传{{ fileTypes.join("、") }} <br />
</p>
</div>
<template v-slot:tip v-if="otherInfo != ''">
<div class="el-upload__tip" style="color: #909bb6;line-height: 18px;">
{{ otherInfo }}
</div>
</template>
</el-upload>
</div>
</template>
<script>
export default {
name: "BgUpload",
// model: {
// prop: "value",
// event: "change",
// },
props: {
modelValue: {
type: Array,
default: () => [],
},
fileTypes: {
type: Array,
default: () => [
"doc",
"docx",
"xls",
"xlsx",
"pdf",
"jpg",
"jpeg",
"png",
],
},
fileMaxSize: {
type: Number,
default: 20, // 单位:M
},
disabled: {
type: Boolean,
default: false,
},
refresh: {
type: Boolean,
default: false,
}, // 是否重新初始化附件(手动刷新组件的附件列表)
customTips: {
type: Boolean,
default: false,
}, // 是否自定义提示
isEasy: {
type: Boolean,
default: false,
},
triggerText: {
type: String,
default: "请上传",
},
triggerIcon: {
type: String,
default: "bg-ic-file",
},
otherInfo: {
type: String,
default: "",
},
limit: {
type: Number,
default: 9999,
},
},
emits: ["update:modelValue","change"],
data() {
return {
fileList: [],
};
},
computed: {
actionDisabled() {
return this.disabled
},
},
watch: {
modelValue() {
let newStr = this.modelValue.map((v) => v.url).join(",");
let oldStr = this.fileList
.map((v) => (v.response && v.response.data) || v.url)
.join(",");
if (newStr !== oldStr) {
this.fileList = [...this.modelValue];
}
},
},
methods: {
initFileList() {
let urls = (this.value && this.value.split(",")) || [];
this.fileList = urls.map((url, index) => {
let temp = url.split("/");
let name = temp[temp.length - 1] || `附件_${index + 1}`;
return { name, url };
});
this.$emit("update:refresh", false);
},
handleBeforeUpload(file) {
if (this.fileList && this.fileList.length >= this.limit) {
this.$message.error(`只允许上传${this.limit}个文件`)
return false
}
let temp = file.name.split(".");
let type = temp[temp.length - 1].toLocaleLowerCase();
let fileTypesOk = this.fileTypes.indexOf(type) > -1||this.fileTypes.length==0;
let fileMaxSizeOk = file.size / 1024 / 1024 <= this.fileMaxSize;
if (!fileTypesOk) {
this.$message.error(
`上传文件只能是${this.fileTypes.join("")}这些格式!`
);
}
if (!fileMaxSizeOk) {
this.$message.error(`上传文件大小不能超过${this.fileMaxSize}M!`);
}
return fileTypesOk && fileMaxSizeOk;
},
handleExceed(file, fileList) {
console.log(file, fileList);
},
handlePreview(val) {
let a = document.createElement("a"); // 生成一个a元素
let event = new MouseEvent("click"); // 创建一个单击事件
a.download = val.name; // 设置图片名称
a.href = val.url || val.response.data; // 将生成的URL设置为a.href属性
a.dispatchEvent(event); // 触发a的单击事件
},
handleRemove(file, fileList) {
this.updateFileList(fileList);
},
handleSuccess(response, file, fileList) {
this.updateFileList(fileList);
},
updateFileList(fileList) {
let values = fileList.map((v) => {
return {
name: v.name,
url: (v.response && v.response.data) || v.url,
};
});
this.fileList = fileList;
this.$emit("update:modelValue", values);
this.$emit("change",values);
},
},
mounted() {
this.fileList = [...this.modelValue];
},
};
</script>
<style>
.bg-upload .el-upload-dragger{
padding: 0;
border: 0;
}
</style>
<template>
<div class="bg-upload-image user-upload-image">
<el-upload
action="#"
ref="upload"
:auto-upload="false"
:on-change="onChangeFile"
:file-list="fileList"
:disabled="disabled"
>
<template v-slot:trigger>
<div class="trigger-content">
<div class="image-trigger" v-if="modelValue">
<img :src="modelValue" />
<div class="refresh-image">
<i class="el-icon-refresh-right"></i>
</div>
</div>
<div class="upload-trigger" v-else>
<span class="upload-icon"></span>
</div>
</div>
</template>
</el-upload>
</div>
</template>
<script>
export default {
name: "BgUploadImage",
props: {
modelValue: {
type: String,
default: "",
},
disabled:{
type:Boolean,
default:false
}
},
emits: ['update:modelValue'],
data(){
return{
fileList:[]
}
},
created(){
},
methods: {
handleAvatarSuccess({ data }) {
this.$emit("update:modelValue", data.visitURL);
},
beforeAvatarUpload(file) {
const isJPG = file.type === "image/jpeg" || file.type === "image/png";
const isLt1M = file.size / 1024 / 1024 < 1;
if (!isJPG) {
this.$message.error("上传的图片只能是 JPG、PNG 格式!");
}
if (!isLt1M) {
this.$message.error("上传的图片大小不能超过 1MB!");
}
return isJPG && isLt1M;
},
onChangeFile(file, fileList){
console.log(file, fileList);
if(!this.beforeAvatarUpload(file.raw)){
return
}
var reader = new FileReader();
reader.readAsDataURL(file.raw); // 一定要传入file格式
reader.onload = () => {
this.$emit("update:modelValue", reader.result);
};
}
},
};
</script>
<style scoped>
.user-upload-image :deep() .el-upload-list{
display: none;
}
</style>
// debounce.js
/**
* 防抖与节流
* @param {function} func 执行函数
* @param {number} time 防抖节流时间
* @param {string} type debounce为防抖,throttle为节流,否则为立即执行
* @param {this} ctx this 的指向
*/
const debounce = (func, time, type, ctx) => {
var timer, lastCall, rtn
// 防抖函数
if (type === 'debounce') {
rtn = (...params) => {
if (timer) clearTimeout(timer)
timer = setTimeout(() => {
func.apply(ctx, params)
}, time)
}
} else if (type === 'throttle') {
// 节流函数
rtn = (...params) => {
const now = new Date().getTime()
if (now - lastCall < time && lastCall) return
lastCall = now
func.apply(ctx, params)
}
} else {
// 立即执行的防抖函数
rtn = (...params) => {
if (timer) clearTimeout(timer)
let callNow = !timer
timer = setTimeout(() => {
timer = null
}, time)
if (callNow) func.apply(ctx, params)
}
}
return rtn
}
export default {
name: 'Debounce',
abstract: true,
props: {
time: {
type: Number,
default: 1000,
},
events: {
type: String,
default: 'click', // 默认点击事件
},
type: {
type: String,
default: 'throttle', // 默认节流
},
},
created() {
this.eventKeys = this.events.split(',') // 分隔事件
this.originMap = {} // 储存事件,用于重新render时与子事件的对比
this.debouncedMap = {} // 储存防抖节流事件
},
render() {
try {
const vnode = this.$slots.default[0]
this.eventKeys.forEach(key => {
const target = vnode.data.on[key]
if (target === this.originMap[key] && this.debouncedMap[key]) {
vnode.data.on[key] = this.debouncedMap[key]
} else if (target) {
this.originMap[key] = target
this.debouncedMap[key] = debounce(target, this.time, this.type, vnode)
vnode.data.on[key] = this.debouncedMap[key] // 重写子组件的事件
}
})
return vnode
} catch (error) {}
},
}
\ No newline at end of file
import { reactive, toRefs,useAttrs } from 'vue'
export function selectTableMixin(){
const state = reactive({
nowSelectData:[],
allSelectData:{}
})
const attrs = useAttrs()
// const nowSelectData = reactive([])
// const allSelectData = reactive({})
const selectData = (val)=>{
console.log(val);
state.nowSelectData = val
console.log('allSelectData');
console.log(state.allSelectData);
console.log(Object.keys(state.allSelectData).length);
}
const initSelectTableData = (data)=>{
return new Promise((reslove,reject)=>{
data.forEach(e => {
if(state.allSelectData[e[attrs.rowKey||'id']]){
delete state.allSelectData[e[attrs.rowKey||'id']]
state.nowSelectData.push(e)
}
});
console.log(state.nowSelectData);
reslove(state.nowSelectData)
})
}
const runPage=()=>{//翻页数据推进
state.nowSelectData.forEach(e => {
state.allSelectData[e[attrs.rowKey||'id']] = e
});
state.nowSelectData = []
}
const clearTable=()=>{//清除选中数据,在页面状态更新时使用
state.allSelectData={}
state.nowSelectData=[]
clearSelection();
}
const dealSelectData=()=>{//最后提交处理数据
state.nowSelectData.forEach(e => {
state.allSelectData[e[attrs.rowKey||'id']] = e
});
return state.allSelectData
}
const { nowSelectData, allSelectData } = toRefs(state)
return{
nowSelectData,
allSelectData,
selectData,
initSelectTableData,
runPage,
clearTable,
dealSelectData
}
}
// export const selectTableMixin = {
// data(){
// return{
// nowSelectData:[],//声明现在选中数组
// allSelectData:{},//全部选中数据obj
// }
// },
// methods: {
// selectData(val){//选中赋值
// console.log(val);
// this.nowSelectData = val
// console.log('allSelectData');
// console.log(this.allSelectData);
// console.log(Object.keys(this.allSelectData).length);
// },
// initSelectTableData(data){//初始化选中table数据
// data.forEach(e => {
// if(this.allSelectData[e.id]){
// delete this.allSelectData[e.id]
// this.nowSelectData.push(e)
// }
// });
// console.log(this.nowSelectData);
// if(this.nowSelectData.length){
// setTimeout(()=>{
// this.toggleRowArrSelection(this.nowSelectData)
// })
// }
// },
// runPage(){//翻页数据推进
// this.nowSelectData.forEach(e => {
// this.allSelectData[e.id] = e
// });
// this.nowSelectData = []
// },
// clearTable(){//清除选中数据,在页面状态更新时使用
// this.allSelectData={}
// this.nowSelectData=[]
// this.clearSelection();
// },
// dealSelectData(){//最后提交处理数据
// this.nowSelectData.forEach(e => {
// this.allSelectData[e.id] = e
// });
// return this.allSelectData
// }
// },
// }
\ No newline at end of file
This diff is collapsed.
/**
* 全局组件
*/
import BgIcon from './bg-icon.vue'
import BgNav from './bg-nav.vue'
import BgList from './bg-list.vue'
import BgDetail from './bg-detail.vue'
import BgFiltrate from './bg-filtrate.vue'
import BgTable from './bg-table.vue'
import BgTablePro from './bg-table-pro.vue'
import BgTableBtn from './bg-table-btn.vue'
import BgTabs from './bg-tabs.vue'
import BgTab from './bg-tab.vue'
import BgLayoutCard from './bg-layout-card.vue'
import BgCard from './bg-card.vue'
import BgInfo from './bg-info.vue'
import BgSteps from './bg-steps.vue'
import BgStep from './bg-step.vue'
import BgBtns from './bg-btns.vue'
import BgUpload from './bg-upload.vue'
import BgUploadImage from './bg-upload-image.vue'
import BgTags from './bg-tags.vue'
import BgSwitch from './bg-switch.vue'
import BgRichText from './bg-rich-text.vue'
import BgCodeEditor from './bg-code-editor.vue'
import BgFilter from './bg-filter.vue'
import BgSort from './bg-sort.vue'
import BgFilterDate from './bg-filter-date.vue'
import bgUserUploadImage from './bg-user-upload-image.vue'
import bgDetailTable from './bg-detail-table.vue'
import bgDetailTable2 from './bg-detail-table2.vue'
import debounce from './debounce'
import BgPagination from './bg-pagination.vue'
import BgInnerTabs from './bg-inner-tabs.vue'
import BgFilterGroup from './bg-filter-group.vue'
import BgTableBtns from './bg-table-btns.vue'
const components = {
BgIcon,//字体图标
BgNav, // 左侧导航
BgList, // 列表页布局
BgDetail, // 详情页布局
BgFiltrate, // 筛选条件
BgTable, // 表格
BgTablePro,
BgTableBtn, // 表格按钮
BgTabs, // 信息块
BgTab, // 信息块(子)
BgLayoutCard, // 带标题的卡片
BgCard, // 详情卡片
BgInfo, // 表格信息
BgSteps, // 步骤条
BgStep, // 步骤条
BgBtns, // 按钮组
BgUpload, // 上传附件
BgUploadImage, // 上传单张图片
BgTags, // 标签
BgSwitch, // 开关
BgRichText, // 富文本
BgCodeEditor,//代码输入
BgFilter, // 首页筛选
BgSort, // 首页排序
BgFilterDate,
bgUserUploadImage,
bgDetailTable,//详情展示组件1
bgDetailTable2,//详情展示组件2
debounce,//防抖组件
BgPagination,// 分页组件
BgInnerTabs,//内部tab
BgFilterGroup,//列表按钮及筛选
BgTableBtns,// 表格按钮组
};
const install = (Vue) => {
for (let name in components) {
Vue.component(name, components[name]);
}
};
if (typeof window !== "undefined" && window.Vue) {
install(window.Vue);
}
export default {
install,
...components,
};
This diff is collapsed.
var downloadFileFormatNew = function (fileUrl) {
/*
input: /apaas/common/docs/image/images/1234_qq234ewr123.png
output: 1234.png
*/
if (fileUrl != "") {
var temp = fileUrl.split('/')[fileUrl.split('/').length-1]
var name = temp.split('_')[0]
var type = temp.split('_')[temp.split('_').length-1].split('.')[1]
if(temp.indexOf('_')==-1){
return name
}else{
return name+'.'+type
}
} else {
return "";
}
}
export default {
downloadFileFormatNew
}
\ No newline at end of file
import { createApp } from 'vue'
import './style.css'
import App from './App.vue'
import bgui from "@/bg-ui";
import "@/bg-ui/index.scss";
createApp(App).use(bgui).mount('#app')
:root {
font-family: Inter, Avenir, Helvetica, Arial, sans-serif;
font-size: 16px;
line-height: 24px;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
a {
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
}
.card {
padding: 2em;
}
#app {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
}
import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import path from "path";
function resolve_path(dir) {
return path.join(__dirname, "./", dir);
}
// https://vitejs.dev/config/
export default defineConfig({
plugins: [vue()],
resolve: {
//别名,代码引入时方便引入
alias: {
"@": resolve_path("src"),
},
},
server: {
port: 3001,
},
});
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment