Commit b4d53406 authored by 水落(YangLei)'s avatar 水落(YangLei)

feat: css框架引入,列表组件开发

parent ed077355
......@@ -27,3 +27,6 @@ $ npm run serve
### 说明
用户名/密码:admin/666666
css 工具函数 地址
https://www.tailwindcss.cn/docs/installation#post-css-7
import Table from './table/index.vue';
export { Table };
<template>
<div :class="['page-header', layout, pageWidth]">
<div class="page-header-wide">
<div class="breadcrumb">
<a-breadcrumb>
<a-breadcrumb-item :key="index" v-for="(item, index) in breadcrumb">
<span>{{item}}</span>
<span>{{ item }}</span>
</a-breadcrumb-item>
</a-breadcrumb>
</div>
<div class="detail">
<div class="main">
<div class="row">
<h1 v-if="showPageTitle && title" class="title">{{title}}</h1>
<div class="action">
<slot name="action"></slot>
</div>
</div>
<div class="row">
<div v-if="this.$slots.content" class="content">
<div v-if="avatar" class="avatar">
<a-avatar :src="avatar" :size="72" />
</div>
<slot name="content"></slot>
</div>
<div v-if="this.$slots.extra" class="extra">
<slot name="extra"></slot>
</div>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
......@@ -63,43 +38,11 @@ export default {
<style lang="less" scoped>
.page-header {
background: @base-bg-color;
padding: 16px 24px;
&.head.fixed {
margin: auto;
max-width: 1400px;
}
.page-header-wide {
.breadcrumb {
margin-bottom: 20px;
}
.detail {
display: flex;
.row {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
}
.avatar {
margin: 0 24px 0 0;
}
.main {
width: 100%;
.title {
font-size: 20px;
color: @title-color;
margin-bottom: 16px;
}
.content {
display: flex;
flex-wrap: wrap;
color: @text-color-second;
}
.extra {
display: flex;
}
}
}
}
}
</style>
<template>
<div>
<div :class="$style.card" v-if="$scopedSlots.search">
<a-form-model layout="inline" :model="queryForm">
<slot name="search" :query="queryForm" />
</a-form-model>
<div class="tw-text-right">
<a-space>
<a-button @click="queryForm = {}">重置</a-button>
<a-button type="primary" @click="getData">查询</a-button>
</a-space>
</div>
</div>
<div class="tw-mt-3" :class="$style.card">
<a-space class="tw-mb-2.5">
<a-button v-if="addBtn" type="primary" key="add" v-bind="addBtn.options" @click="add">
{{ addBtn.text || '新建' }}
</a-button>
<template v-for="btn of buttons">
<a-button :type="btn.primary" :key="btn.text">{{ btn.text }}</a-button>
</template>
</a-space>
<a-table :data-source="data" :loading="loading" v-bind="$attrs">
<slot />
</a-table>
</div>
<!-- 新增 -->
<a-drawer
:title="addBtn.title || '新建'"
placement="right"
:visible="addVisible"
@close="addDrawerClose"
v-if="addBtn"
:maskClosable="addBtn.maskClosable"
>
<slot name="add" />
</a-drawer>
</div>
</template>
<script>
import { request, METHOD } from '@/utils/requestUtil';
import BASE_URL from '@/utils/baseUrlUtil';
const data = [
{
key: '1',
name: 'John Brown',
age: 32,
address: 'New York No. 1 Lake Park',
tags: ['nice', 'developer'],
},
{
key: '2',
name: 'Jim Green',
age: 42,
address: 'London No. 1 Lake Park',
tags: ['loser'],
},
{
key: '3',
name: 'Joe Black',
age: 32,
address: 'Sidney No. 1 Lake Park',
tags: ['cool', 'teacher'],
},
];
export default {
props: {
url: String,
buttons: {
type: Array,
default: () => [],
},
addBtn: {
type: Object,
},
},
data() {
return {
data: [],
queryForm: {},
loading: false,
addVisible: false,
};
},
mounted() {
this.getData();
},
methods: {
async getData() {
console.log('查询');
this.loading = true;
setTimeout(() => {
this.data = data;
this.loading = false;
}, 1000);
// const res = await request(`${BASE_URL}/${this.url}`, METHOD.GET);
// console.log(res);
},
add() {
this.addVisible = true;
},
addDrawerClose() {
this.addVisible = false;
},
},
};
</script>
<style module>
.card {
@apply tw-bg-white tw-rounded-md tw-p-2.5;
}
</style>
......@@ -7,6 +7,7 @@ import { initRouter } from './router';
import VueI18n from 'vue-i18n';
import {accountModule, settingModule} from './pages/frame/store';
import App from './App.vue';
import "tailwindcss/tailwind.css"
import 'animate.css/source/animate.css';
import './theme/index.less';
......
import Mock from 'mockjs';
Mock.mock(`${process.env.VUE_APP_API_BASE_URL}/table`, 'post', () => ({
data: {
pageNum: 1,
pageSize: 10,
records: [
{
regionId: 1403455275008032,
regionName: '陈浩玮',
organizationId: 146,
organizationName: '一级系统:巴基斯坦公司',
remark: '123',
editorId: 1,
editorName: '管理员',
editTime: '2021-03-16 22:20:25',
},
{
regionId: 1403455136596000,
regionName: '地点测试',
organizationId: 212,
organizationName: '吴国',
remark: '123',
editorId: 1,
editorName: '管理员',
editTime: '2021-03-16 22:19:19',
},
{
regionId: 1400871246102560,
regionName: '陈浩玮测试',
organizationId: 212,
organizationName: '吴国',
remark: '321',
editorId: 1,
editorName: '管理员',
editTime: '2021-03-02 16:04:25',
},
{
regionId: 1396338094768160,
regionName: '文件地区',
organizationId: 208,
organizationName: '扩普发展',
remark: '123213123123213213223123123',
editorId: 1,
editorName: '管理员',
editTime: '2021-03-02 15:37:11',
},
],
total: 4,
size: 10,
current: 1,
pages: 1,
},
code: 'sys.success',
message: null,
traceId: '1805b654e82a4f2a8357d2f8922d9b19',
meta: null,
}));
<template>
<div class="page-layout">
<page-header ref="pageHeader" :style="`margin-top: ${multiPage ? 0 : -24}px`" :breadcrumb="breadcrumb" :title="pageTitle" :logo="logo" :avatar="avatar">
<page-header
ref="pageHeader"
:style="`margin-top: ${multiPage ? 0 : -24}px`"
:breadcrumb="breadcrumb"
:title="pageTitle"
:logo="logo"
:avatar="avatar"
>
<slot name="action" slot="action"></slot>
<slot slot="content" name="headerContent"></slot>
<div slot="content" v-if="!this.$slots.headerContent && desc">
<p>{{ desc }}</p>
<div v-if="this.linkList" class="link">
<template v-for="(link, index) in linkList">
<a :key="index" :href="link.href">
<a-icon :type="link.icon" />{{ link.title }}
</a>
<a :key="index" :href="link.href"> <a-icon :type="link.icon" />{{ link.title }} </a>
</template>
</div>
</div>
......@@ -76,7 +81,7 @@ export default {
let breadcrumb = page && page.breadcrumb;
if (breadcrumb) {
let i18nBreadcrumb = [];
breadcrumb.forEach((item) => {
breadcrumb.forEach(item => {
i18nBreadcrumb.push(this.$t(item));
});
return i18nBreadcrumb;
......@@ -95,8 +100,8 @@ export default {
const path = this.$route.path;
let breadcrumb = [];
routes
.filter((item) => path.includes(item.path))
.forEach((route) => {
.filter(item => path.includes(item.path))
.forEach(route => {
const path = route.path.length === 0 ? '/home' : route.path;
breadcrumb.push(this.$t(getI18nKey(path)));
});
......@@ -119,11 +124,7 @@ export default {
</script>
<style lang="less">
.page-header {
margin: 0 -24px 0;
}
.link {
/*margin-top: 16px;*/
line-height: 24px;
a {
font-size: 14px;
......@@ -136,9 +137,7 @@ export default {
}
.page-content {
position: relative;
padding: 24px 0 0;
&.side {
}
&.head.fixed {
margin: 0 auto;
max-width: 1400px;
......
<template>
<h1>Menu Management</h1>
<my-table url="table" addBtn="addBtn">
<template #search="{query}">
<a-form-model-item label="所属组织">
<a-input v-model="query.name" />
</a-form-model-item>
<a-form-model-item label="所属组织">
<a-input v-model="query.name" />
</a-form-model-item>
<a-form-model-item label="所属组织">
<a-input v-model="query.name" />
</a-form-model-item>
<a-form-model-item label="所属组织">
<a-input v-model="query.name" />
</a-form-model-item>
<a-form-model-item label="所属组织">
<a-input v-model="query.name" />
</a-form-model-item>
<a-form-model-item label="所属组织">
<a-input v-model="query.name" />
</a-form-model-item>
</template>
<template #add>
<a-input v-model="add.name" />
</template>
<a-table-column title="姓名" data-index="name" />
<a-table-column title="操作">
<template #default="row">
<span>
<a> {{ row }}</a>
<a-divider type="vertical" />
<a>Delete</a>
</span>
</template>
</a-table-column>
</my-table>
</template>
<script>
export default {
data: () => ({
add: {},
addBtn: {},
}),
};
</script>
<template>
<my-table url="table" :search="search" />
</template>
<script>
export default {
data: () => ({
search: [
{
type: 'input',
key: 'name',
label: '所属组织',
options: {
placeholder: '请选择所属组织',
},
},
{
type: 'input',
key: 'address',
label: '地区名称',
},
],
}),
};
</script>
import VueI18nPlugin from './vueI18nPlugin'
import AuthorityPlugin from './authorityPlugin'
import TabsPagePlugin from './tabsPagePlugin'
import VueI18nPlugin from './vueI18nPlugin';
import AuthorityPlugin from './authorityPlugin';
import TabsPagePlugin from './tabsPagePlugin';
import { Table } from '@/components';
const Plugins = {
install: function (Vue) {
Vue.use(VueI18nPlugin)
Vue.use(AuthorityPlugin)
Vue.use(TabsPagePlugin)
}
}
install: function(Vue) {
Vue.use(VueI18nPlugin);
Vue.use(AuthorityPlugin);
Vue.use(TabsPagePlugin);
Vue.component('my-table', Table);
},
};
export default Plugins;
module.exports = {
purge: ['./src/*.vue'],
darkMode: false, // or 'media' or 'class'
theme: {
extend: {},
},
variants: {
extend: {},
},
plugins: [],
prefix: 'tw-',
corePlugins: {
preflight: false,
fixed: false,
},
};
let path = require("path");
const webpack = require("webpack");
const ThemeColorReplacer = require("webpack-theme-color-replacer");
const globalConfig = require("./src/config/global.config");
const settingConfig = require("./src/config/setting.config");
const cssResolverConfig = require("./src/config/cssResolver.config");
const { getMenuColors, getAntdColors, getThemeToggleColors, getFunctionalColors } = require('./src/utils/colorUtil');
let path = require('path');
const webpack = require('webpack');
const ThemeColorReplacer = require('webpack-theme-color-replacer');
const globalConfig = require('./src/config/global.config');
const settingConfig = require('./src/config/setting.config');
const cssResolverConfig = require('./src/config/cssResolver.config');
const {
getMenuColors,
getAntdColors,
getThemeToggleColors,
getFunctionalColors,
} = require('./src/utils/colorUtil');
const CompressionWebpackPlugin = require("compression-webpack-plugin");
const CompressionWebpackPlugin = require('compression-webpack-plugin');
const productionGzipExtensions = ["js", "css"];
const isProd = process.env.NODE_ENV === "production";
const productionGzipExtensions = ['js', 'css'];
const isProd = process.env.NODE_ENV === 'production';
const theme = settingConfig.theme;
function getThemeColors(color, $theme) {
const _color = color || theme.color
const mode = $theme || theme.mode
const replaceColors = getThemeToggleColors(_color, mode)
const _color = color || theme.color;
const mode = $theme || theme.mode;
const replaceColors = getThemeToggleColors(_color, mode);
const themeColors = [
...replaceColors.mainColors,
...replaceColors.subColors,
......@@ -26,16 +31,16 @@ function getThemeColors(color, $theme) {
...replaceColors.functionalColors.success,
...replaceColors.functionalColors.warning,
...replaceColors.functionalColors.error,
]
return themeColors
];
return themeColors;
}
function modifyVars(color) {
let _color = color || theme.color
const palettes = getAntdColors(_color, theme.mode)
const menuColors = getMenuColors(_color, theme.mode)
const { success, warning, error } = getFunctionalColors(theme.mode)
const primary = palettes[5]
let _color = color || theme.color;
const palettes = getAntdColors(_color, theme.mode);
const menuColors = getMenuColors(_color, theme.mode);
const { success, warning, error } = getFunctionalColors(theme.mode);
const primary = palettes[5];
return {
'primary-color': primary,
'primary-1': palettes[0],
......@@ -66,74 +71,74 @@ function modifyVars(color) {
'layout-trigger-background': menuColors[2],
'btn-danger-bg': error[4],
'btn-danger-border': error[4],
...globalConfig.theme[theme.mode]
}
...globalConfig.theme[theme.mode],
};
}
// 修正 webpack-theme-color-replacer 插件提取的 css 结果
function resolveCss(output, srcArr) {
let regExps = []
let regExps = [];
// 提取 resolve 配置中所有的正则配置
Object.keys(cssResolverConfig).forEach(key => {
let isRegExp = false
let reg = {}
let isRegExp = false;
let reg = {};
try {
reg = eval(key)
isRegExp = reg instanceof RegExp
reg = eval(key);
isRegExp = reg instanceof RegExp;
} catch (e) {
isRegExp = false
isRegExp = false;
}
if (isRegExp) {
regExps.push([reg, cssResolverConfig[key]])
regExps.push([reg, cssResolverConfig[key]]);
}
})
});
// 去重
srcArr = dropDuplicate(srcArr)
srcArr = dropDuplicate(srcArr);
// 处理 css
let outArr = []
let outArr = [];
srcArr.forEach(text => {
// 转换为 css 对象
let cssObj = parseCssObj(text)
let cssObj = parseCssObj(text);
// 根据selector匹配配置,匹配成功,则按配置处理 css
if (cssResolverConfig[cssObj.selector] != undefined) {
let cfg = cssResolverConfig[cssObj.selector]
let cfg = cssResolverConfig[cssObj.selector];
if (cfg) {
outArr.push(cfg.resolve(text, cssObj))
outArr.push(cfg.resolve(text, cssObj));
}
} else {
let cssText = ''
let cssText = '';
// 匹配不成功,则测试是否有匹配的正则配置,有则按正则对应的配置处理
for (let regExp of regExps) {
if (regExp[0].test(cssObj.selector)) {
let cssCfg = regExp[1]
cssText = cssCfg ? cssCfg.resolve(text, cssObj) : ''
break
let cssCfg = regExp[1];
cssText = cssCfg ? cssCfg.resolve(text, cssObj) : '';
break;
}
// 未匹配到正则,则设置 cssText 为默认的 css(即不处理)
cssText = text
cssText = text;
}
if (cssText != '') {
outArr.push(cssText)
outArr.push(cssText);
}
}
})
output = outArr.join('\n')
return output
});
output = outArr.join('\n');
return output;
}
// 数组去重
function dropDuplicate(arr) {
let map = {}
let r = []
let map = {};
let r = [];
for (let s of arr) {
if (!map[s]) {
r.push(s)
map[s] = 1
r.push(s);
map[s] = 1;
}
}
return r
return r;
}
/**
......@@ -146,42 +151,44 @@ function dropDuplicate(arr) {
* }}
*/
function parseCssObj(cssText) {
let css = {}
const ruleIndex = cssText.indexOf('{')
css.selector = cssText.substring(0, ruleIndex)
const ruleBody = cssText.substring(ruleIndex + 1, cssText.length - 1)
const rules = ruleBody.split(';')
css.rules = rules
css.toText = function () {
let body = ''
this.rules.forEach(item => { body += item + ';' })
return `${this.selector}{${body}}`
}
return css
let css = {};
const ruleIndex = cssText.indexOf('{');
css.selector = cssText.substring(0, ruleIndex);
const ruleBody = cssText.substring(ruleIndex + 1, cssText.length - 1);
const rules = ruleBody.split(';');
css.rules = rules;
css.toText = function() {
let body = '';
this.rules.forEach(item => {
body += item + ';';
});
return `${this.selector}{${body}}`;
};
return css;
}
const assetsCDN = {
// webpack build externals cdn预加载使用
externals: {
vue: "Vue",
"vue-router": "VueRouter",
vuex: "Vuex",
axios: "axios",
nprogress: "NProgress",
clipboard: "ClipboardJS",
"@antv/data-set": "DataSet",
"js-cookie": "Cookies",
vue: 'Vue',
'vue-router': 'VueRouter',
vuex: 'Vuex',
axios: 'axios',
nprogress: 'NProgress',
clipboard: 'ClipboardJS',
'@antv/data-set': 'DataSet',
'js-cookie': 'Cookies',
},
css: [],
js: [
"//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js",
"//cdn.jsdelivr.net/npm/vue-router@3.3.4/dist/vue-router.min.js",
"//cdn.jsdelivr.net/npm/vuex@3.4.0/dist/vuex.min.js",
"//cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js",
"//cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.min.js",
"//cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/clipboard.min.js",
"//cdn.jsdelivr.net/npm/@antv/data-set@0.11.4/build/data-set.min.js",
"//cdn.jsdelivr.net/npm/js-cookie@2.2.1/src/js.cookie.min.js",
'//cdn.jsdelivr.net/npm/vue@2.6.11/dist/vue.min.js',
'//cdn.jsdelivr.net/npm/vue-router@3.3.4/dist/vue-router.min.js',
'//cdn.jsdelivr.net/npm/vuex@3.4.0/dist/vuex.min.js',
'//cdn.jsdelivr.net/npm/axios@0.19.2/dist/axios.min.js',
'//cdn.jsdelivr.net/npm/nprogress@0.2.0/nprogress.min.js',
'//cdn.jsdelivr.net/npm/clipboard@2.0.6/dist/clipboard.min.js',
'//cdn.jsdelivr.net/npm/@antv/data-set@0.11.4/build/data-set.min.js',
'//cdn.jsdelivr.net/npm/js-cookie@2.2.1/src/js.cookie.min.js',
],
};
......@@ -204,21 +211,21 @@ module.exports = {
*
* theme.less 定义的变量预先导入 直接使用
*/
"style-resources-loader": {
preProcessor: "less",
'style-resources-loader': {
preProcessor: 'less',
//这个是绝对路径,不能使用 alias中配置的别名路径,如@表示的src
patterns: [path.resolve(__dirname, "./src/theme/default.less")],
patterns: [path.resolve(__dirname, './src/theme/default.less')],
},
},
configureWebpack: (config) => {
config.entry.app = ["babel-polyfill", "whatwg-fetch", "./src/main.js"];
configureWebpack: config => {
config.entry.app = ['babel-polyfill', 'whatwg-fetch', './src/main.js'];
config.performance = {
hints: false,
};
config.plugins.push(
new ThemeColorReplacer({
//Optional. output css file name, suport [contenthash] and [hash].
fileName: "css/theme-colors-[contenthash:8].css",
fileName: 'css/theme-colors-[contenthash:8].css',
//matchColors: Array<string> Colors array for extracting css file.
//Css rules which have any one of these colors will be extracted out.
//抽取所有相关颜色做替换
......@@ -228,7 +235,7 @@ module.exports = {
//resolveCss: Function(resultCss : string) : string Optional. Resolve result css code as you wish.
//转换原有CSS中的特定样式为自定义样式
resolveCss,
})
}),
);
// Ignore all locale files of moment.js
config.plugins.push(new webpack.IgnorePlugin(/^\.\/locale$/, /moment$/));
......@@ -237,11 +244,11 @@ module.exports = {
// add `CompressionWebpack` plugin to webpack plugins
config.plugins.push(
new CompressionWebpackPlugin({
algorithm: "gzip",
test: new RegExp("\\.(" + productionGzipExtensions.join("|") + ")$"),
algorithm: 'gzip',
test: new RegExp('\\.(' + productionGzipExtensions.join('|') + ')$'),
threshold: 10240,
minRatio: 0.8,
})
}),
);
}
// if prod, add externals
......@@ -249,17 +256,17 @@ module.exports = {
config.externals = assetsCDN.externals;
}
},
chainWebpack: (config) => {
chainWebpack: config => {
// 生产环境下关闭css压缩的 colormin 项,因为此项优化与主题色替换功能冲突
if (isProd) {
config.plugin("optimize-css").tap((args) => {
config.plugin('optimize-css').tap(args => {
args[0].cssnanoOptions.preset[1].colormin = false;
return args;
});
}
// 生产环境下使用CDN
if (isProd) {
config.plugin("html").tap((args) => {
config.plugin('html').tap(args => {
args[0].cdn = assetsCDN;
return args;
});
......@@ -273,10 +280,13 @@ module.exports = {
javascriptEnabled: true,
},
},
postcss: {
plugins: [require('tailwindcss')],
},
},
},
publicPath: process.env.VUE_APP_PUBLIC_PATH,
outputDir: "dist",
assetsDir: "static",
outputDir: 'dist',
assetsDir: 'static',
productionSourceMap: false,
};
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