Browse Source

增加提示组件、复制功能

develop
zq 3 months ago
parent
commit
0397897ca9
  1. 1470
      examples/src/App.vue
  2. 7
      packages/GuipFormItem/index.js
  3. 52
      packages/GuipFormItem/src/index.vue
  4. 170
      packages/GuipMessage/GuipMessage.vue
  5. 94
      packages/GuipMessage/index.js
  6. BIN
      packages/assets/dropDown_expand.png
  7. BIN
      packages/assets/message_Success.png
  8. BIN
      packages/assets/message_Warning.png
  9. BIN
      packages/assets/message_error.png
  10. 41
      packages/index.js
  11. 85
      packages/styles/component.scss
  12. 77
      packages/utils/clipboard.js
  13. 32
      packages/utils/common.js
  14. 19
      packages/utils/dirClipBoard.js
  15. 11
      packages/utils/eventBus.js
  16. 17
      packages/utils/headerIcon.js
  17. 93
      packages/utils/request.js

1470
examples/src/App.vue

File diff suppressed because it is too large

7
packages/GuipFormItem/index.js

@ -0,0 +1,7 @@
import GuipFormItem from './src/index.vue'
GuipFormItem.install = function(Vue) {
Vue.component(GuipFormItem.name || 'GuipFormItem', GuipFormItem)
}
export default GuipFormItem

52
packages/GuipFormItem/src/index.vue

@ -0,0 +1,52 @@
<template>
<div
:class="[{'column':column},{'error':hasError},{'w510':addClass=='w510'},{'w388':addClass=='w388'},'form-item1']">
<div class="form-item-top">
<label v-if="label" for="">{{ label }}
<img src="../../assets/require.svg" v-if="required" alt="">
</label>
<template >
<slot name="formLeft"></slot>
</template>
<template >
<slot name="formRight"></slot>
</template>
</div>
<div class="form-item-bottom">
<template >
<slot name="formDom"></slot>
</template>
</div>
</div>
</template>
<script>
export default {
name: 'GuipFormItem',
props:['label','required','addClass','column'],
data() {
return {
hasError: false,
//
classList:{
'w510':'w510',
'w388':'w388'
}
}
},
computed: {
// dynamicClasses() {
// return {
// active: this.isActive, // isActivetrue'active'
// error: this.hasError, // hasErrortrue'error'
// highlighted: this.isHighlighted, // isHighlightedtrue'highlighted'
// };
// }
},
mounted(){
// console.log(this.required,'required----');
}
}
</script>
<style lang="scss" scoped>
</style>

170
packages/GuipMessage/GuipMessage.vue

@ -0,0 +1,170 @@
<template>
<transition name="fade">
<div v-if="visible" class="custom-message" :class="[type, position]">
<img v-if="showImage" :src="imageUrl" class="message-image" />
<i v-else :class="iconClass"></i>
<span class="message-content">{{ message }}</span>
</div>
</transition>
</template>
<script>
export default {
name: 'CustomMessage',
props: {
type: {
type: String,
default: 'info',
validator: value => ['success', 'warning', 'error', 'info'].includes(value)
},
message: {
type: String,
required: true
},
duration: {
type: Number,
default: 3000
},
position: {
type: String,
default: 'top-center',
validator: value => ['top-center', 'top-right', 'top-left', 'bottom-center', 'bottom-right', 'bottom-left'].includes(value)
},
showImage: {
type: Boolean,
default: false
},
imageUrl: {
type: String,
default: ''
},
iconClass: {
type: String,
default: ''
}
},
data() {
return {
visible: false
}
},
mounted() {
this.visible = true
this.startTimer()
},
methods: {
startTimer() {
if (this.duration > 0) {
setTimeout(() => {
this.close()
}, this.duration)
}
},
close() {
this.visible = false
setTimeout(() => {
this.$destroy()
this.$el.parentNode.removeChild(this.$el)
}, 300)
}
}
}
</script>
<style scoped>
.custom-message {
position: fixed;
z-index: 9999;
display: flex;
align-items: center;
padding: 14px 21px;
transition: all 0.3s;
font-size: 14px;
font-weight: normal;
line-height: normal;
letter-spacing: 0.08em;
color: #626573;
border-radius: 4px;
background: #FFFFFF;
/* 阴影/低阴影 */
box-shadow: 0px 3px 8px 0px rgba(0, 0, 0, 0.16);
}
/* 位置样式 */
.top-center {
top: 20px;
left: 50%;
transform: translateX(-50%);
}
.top-right {
top: 20px;
right: 20px;
}
.top-left {
top: 20px;
left: 20px;
}
.bottom-center {
bottom: 20px;
left: 50%;
transform: translateX(-50%);
}
.bottom-right {
bottom: 20px;
right: 20px;
}
.bottom-left {
bottom: 20px;
left: 20px;
}
/* 类型样式 */
/* .success {
background-color: #f6ffed;
border: 1px solid #b7eb8f;
color: #52c41a;
}
.info {
background-color: #e6f7ff;
border: 1px solid #91d5ff;
color: #1890ff;
}
.warning {
background-color: #fffbe6;
border: 1px solid #ffe58f;
color: #faad14;
}
.error {
background-color: #fff2f0;
border: 1px solid #ffccc7;
color: #f5222d;
} */
.message-image {
width: 18px;
height: 18px;
margin-right: 10px;
}
/* 动画效果 */
.fade-enter-active, .fade-leave-active {
transition: opacity 0.3s, transform 0.3s;
}
.fade-enter, .fade-leave-to {
opacity: 0;
}
.fade-enter.top-center, .fade-leave-to.top-center {
transform: translate(-50%, -20px);
}
.fade-enter.top-right, .fade-leave-to.top-right,
.fade-enter.top-left, .fade-leave-to.top-left {
transform: translateY(-20px);
}
.fade-enter.bottom-center, .fade-leave-to.bottom-center {
transform: translate(-50%, 20px);
}
.fade-enter.bottom-right, .fade-leave-to.bottom-right,
.fade-enter.bottom-left, .fade-leave-to.bottom-left {
transform: translateY(20px);
}
</style>

94
packages/GuipMessage/index.js

@ -0,0 +1,94 @@
import Vue from 'vue'
import GuipMessage from './GuipMessage.vue'
// 默认图标配置
const defaultIcons = {
success: 'icon-success', // 替换为你的图标类名
info: 'icon-info',
warning: 'icon-warning',
error: 'icon-error'
}
// 默认图片配置
const defaultImages = {
success: require('../assets/message_Success.png'),
info: require('../assets/message_Warning.png'),
warning: require('../assets/message_Warning.png'),
error: require('../assets/message_error.png')
}
const MessageConstructor = Vue.extend(GuipMessage)
// let messageInstance = null
let messageQueue = []
function showMessage(options) {
if (typeof options === 'string') {
options = {
message: options
}
}
// 合并默认配置
const config = {
type: 'info',
duration: 3000,
position: 'top-center',
showImage: true,
...options
}
// 设置图标或图片
if (config.showImage) {
config.imageUrl = options.imageUrl || defaultImages[config.type]
} else {
config.iconClass = options.iconClass || defaultIcons[config.type]
}
// 创建实例
const instance = new MessageConstructor({
propsData: config
})
// 挂载到DOM
instance.$mount()
document.body.appendChild(instance.$el)
// 添加到队列
messageQueue.push(instance)
// 返回关闭方法
return {
close: () => instance.close()
}
}
function install(Vue, options = {}) {
// 合并默认配置
if (options.icons) {
Object.assign(defaultIcons, options.icons)
}
if (options.images) {
Object.assign(defaultImages, options.images)
}
// 添加全局方法
Vue.prototype.$Message = showMessage
Vue.prototype.$Message.closeAll = () => {
messageQueue.forEach(instance => instance.close())
messageQueue = []
}
// 添加快捷方法
const types = ['success', 'info', 'warning', 'error']
types.forEach(type => {
Vue.prototype.$Message[type] = (message, options = {}) => {
return showMessage({
message,
type,
...options
})
}
})
}
export default { install }

BIN
packages/assets/dropDown_expand.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 673 B

After

Width:  |  Height:  |  Size: 297 B

BIN
packages/assets/message_Success.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 802 B

BIN
packages/assets/message_Warning.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 654 B

BIN
packages/assets/message_error.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 746 B

41
packages/index.js

@ -13,12 +13,32 @@ import GuipTable from './GuipTable';
import GuipSwitch from './GuipSwitch';
import GuipTooltip from './GuipToolTip';
import GuipDialog from './GuipDialog';
import 'core-js/stable'
import GuipMessage from './GuipMessage/index';
import GuipFormItem from './GuipFormItem';
// 表格头部 添加自定义小图标 *
// import HeaderIcon from './utils/headerIcon'
// Vue.mixin(HeaderIcon)
// 复制到粘贴板
import clipboard from './utils/dirClipBoard';
import { modernCopyToClipboard } from './utils/clipboard';
import 'core-js/stable';
import 'element-ui/lib/theme-chalk/index.css'; // 如果依赖Element
import './styles/index.css'; // 全局引入
import './styles/common.scss'; // 全局引入
import './styles/component.scss'; // 全局引入
// import EventBus, {
// $on,
// $once,
// $off,
// $emit
// } from './utils/eventBus'
const components = [
GuipButton,
GroupFormBtns,
@ -34,10 +54,23 @@ const components = [
GuipTable,
GuipTooltip,
GuipSwitch,
GuipDialog
GuipDialog,
GuipFormItem
]
const install = function (Vue) {
// 挂载到Vue原型
// Vue.prototype.$eventBus = EventBus
// Vue.prototype.$on = $on
// Vue.prototype.$emit = $emit
// Vue.prototype.$off = $off
// Vue.prototype.$once = $once
// 复制
Vue.use(GuipMessage)
Vue.prototype.$copy = modernCopyToClipboard;
Vue.use(clipboard);
components.forEach(component => {
if (!component.name) {
throw new Error(`Component name is required: ${component}`)
@ -45,6 +78,7 @@ const install = function (Vue) {
Vue.component(component.name, component)
})
}
console.log(install,'install---');
export default {
install,
@ -62,5 +96,6 @@ export default {
GuipTable,
GuipTooltip,
GuipSwitch,
GuipDialog
GuipDialog,
GuipFormItem
}

85
packages/styles/component.scss

@ -215,6 +215,7 @@
}
}
.guip-svg-icon-wrapper .svg-icon ::v-deep path {
fill: currentColor;
}
@ -296,4 +297,86 @@
font-size: 12px;
margin-left: 10px;
}
}
}
// 自定义下拉选择框组件 start
.guip-custom-select {
height: 38px;
position: relative;
font-family: Arial, sans-serif;
.select-trigger {
border-radius: 2px;
opacity: 1;
background: #FFFFFF;
border: 1px solid #DFE2E6;
width: 100%;
height: 40px;
box-sizing: border-box;
padding: 10px 12px;
cursor: pointer;
display: flex;
justify-content: space-between;
align-items: center;
}
.is-open .select-trigger {
border: 1px solid #006AFF;
transition: all .5s;
outline: 3px solid #D8E9FA;
}
.select-trigger:hover {
border-color: #006AFF;
transition: all .5s;
}
.arrow-icon {
width: 12px;
}
.select-dropdown {
position: absolute;
top: 100%;
right: 0;
width: 100%;
border: 1px solid #ccc;
box-sizing: border-box;
border-radius: 4px;
background-color: #fff;
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
z-index: 1000;
margin-top: 4px;
max-height: 384px;
overflow-y: auto;
padding: 0 12px 12px 12px;
}
.dropdown-item {
padding: 12px 10px;
cursor: pointer;
}
.dropdown-item:hover {
background: #F6F7FA;
}
.dropdown-item.is-selected {
background-color: #F6F7FA;
color: #006AFF;
}
/* 展开收起动画 */
.slide-fade-enter-active,
.slide-fade-leave-active {
transition: all 0.3s ease;
}
.slide-fade-enter-from,
.slide-fade-leave-to {
opacity: 0;
transform: translateY(-10px);
}
}
// 自定义下拉选择框组件 end

77
packages/utils/clipboard.js

@ -0,0 +1,77 @@
/**
* 复制文本到剪贴板
* @param {string} text 要复制的文本
* @param {Object} options 配置选项
* @param {string} options.successMsg 成功提示信息
* @param {string} options.errorMsg 失败提示信息
* @param {Vue} options.vm Vue实例(用于调用$Message)
* @returns {Promise<boolean>} 是否复制成功
*/
export function copyToClipboard(text, options = {}) {
const {
successMsg = '复制成功',
errorMsg = '复制失败,请手动复制',
vm = null
} = options;
return new Promise((resolve) => {
// 创建textarea元素
const textarea = document.createElement('textarea');
textarea.value = text;
textarea.style.position = 'fixed'; // 防止页面滚动
document.body.appendChild(textarea);
textarea.select();
try {
// 执行复制命令
const successful = document.execCommand('copy');
console.log(vm,'vm.$Message---');
if (successful) {
if (vm && vm.$Message) {
vm.$Message.success(successMsg);
} else {
console.log(successMsg);
}
resolve(true);
} else {
throw new Error('Copy command was unsuccessful');
}
} catch (err) {
console.error('复制失败11:', err);
if (vm && vm.$Message) {
vm.$Message.error(errorMsg);
} else {
console.error(errorMsg);
}
resolve(false);
} finally {
document.body.removeChild(textarea);
}
});
}
/**
* @param {string} text 要复制的文本
* @param {Object} options 配置选项
* @returns {Promise<boolean>} 是否复制成功
*/
export async function modernCopyToClipboard(text, options = {}) {
const {
successMsg = '复制成功',
errorMsg = '复制失败,请手动复制',
vm = null
} = options;
if (navigator.clipboard && window.isSecureContext) {
// 注意:localhost 可能会无效 或者 提示复制失败,但是其实已经复制到粘贴板
await navigator.clipboard.writeText(text);
if (vm && vm.$Message) {
vm.$Message.success(successMsg);
} else {
console.log(errorMsg);
}
} else {
return copyToClipboard(text, options);
}
}
export default modernCopyToClipboard;

32
packages/utils/common.js

@ -0,0 +1,32 @@
// 设置页面元素对应高亮
export function setHighActive(dom) {
const ele = document.getElementById(dom)
ele.classList.add('ceshi')
ele.scrollIntoView({behavior:'smooth',block:'start'})
setTimeout(()=>{
ele.classList.remove('ceshi')
},1000)
}
export function getServicePriceDesc(price, price_unit, unit_num) {
let unit = 0;
let unit_str = "";
if (unit_num == 1) return price + price_unit +'/篇';
if (unit_num/10000 < 10) {
unit = Math.ceil(unit_num/10000);
unit_str = unit == 1 ? '万' : unit+'万';
}
if (unit_num/1000 < 10) {
unit = Math.ceil(unit_num/1000);
unit_str = unit == 1 ? '千' : unit+'千';
}
if (unit_num/100 < 10) {
unit = Math.ceil(unit_num/100);
unit_str = unit == 1 ? '百' : unit+'百';
}
return price + price_unit + "/" +unit_str + "字符";
}

19
packages/utils/dirClipBoard.js

@ -0,0 +1,19 @@
import modernCopyToClipboard from './clipboard';
export default {
install(Vue) {
Vue.directive('clipboard', {
bind(el, binding) {
el.style.cursor = 'pointer';
el.addEventListener('click', async () => {
const text = binding.value || el.innerText;
const options = {
vm: binding.instance,
...(binding.arg || {})
};
await modernCopyToClipboard(text, options);
});
}
});
}
};

11
packages/utils/eventBus.js

@ -0,0 +1,11 @@
import Vue from 'vue'
// 创建全局事件总线
const EventBus = new Vue()
// 封装常用方法
export const $on = EventBus.$on.bind(EventBus)
export const $once = EventBus.$once.bind(EventBus)
export const $off = EventBus.$off.bind(EventBus)
export const $emit = EventBus.$emit.bind(EventBus)
export default EventBus

17
packages/utils/headerIcon.js

@ -0,0 +1,17 @@
export default {
methods: {
renderHeaderWithIcon(h, { column }, iconPath) {
return h('div', [
column.label,
h('img', {
attrs: { src: iconPath },
style: {
width: '10px',
height: '10px',
marginLeft: '3px',
}
})
])
}
}
}

93
packages/utils/request.js

@ -0,0 +1,93 @@
// src/utils/request.js
import axios from "axios";
// 创建 axios 实例
const service = axios.create({
baseURL: process.env.VUE_APP_BASE_API, // 从环境变量中读取 API 基础地址
timeout: 60000, // 请求超时时间
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
},
});
// 请求拦截器
service.interceptors.request.use(
(config) => {
// 在发送请求之前做一些处理,例如添加 token
const token = localStorage.getItem("token");
if (token) {
config.headers["Auth"] = `${token}`;
}
return config;
},
(error) => {
// 对请求错误做些什么
return Promise.reject(error);
}
);
// 响应拦截器
service.interceptors.response.use(
(response) => {
// 对响应数据做一些处理
const res = response.data;
if (!res.status) {
// 如果返回的 status 不是 true,则视为错误
// return Promise.reject(new Error(res.info || "请求失败"));
}
return res;
},
(error) => {
// 对响应错误做些什么
if (error.response) {
switch (error.response.status) {
case 401:
// 未授权,跳转到登录页
window.location.href = "/login";
break;
case 404:
// 资源未找到
console.error("资源未找到");
break;
case 500:
// 服务器错误
console.error("服务器错误");
break;
default:
console.error("请求失败", error.message);
}
}
return Promise.reject(error);
}
);
/**
* 封装请求方法
* @param {string} method 请求方法 (GET, POST, PUT, DELETE )
* @param {string} url 请求地址
* @param {object} data 请求参数
* @param {object} config 其他 axios 配置
* @returns {Promise} 返回请求结果
*/
const request = (method, url, data = {}, config = {}) => {
const lowerCaseMethod = method.toLowerCase();
if (lowerCaseMethod === "get") {
// GET 请求将参数拼接到 URL 上
return service({
method: "get",
url,
params: data,
...config,
});
} else {
// 其他请求(POST, PUT, DELETE 等)将参数放在请求体中
return service({
method: lowerCaseMethod,
url,
data,
...config,
});
}
};
export default request;
Loading…
Cancel
Save