diff --git a/examples/src/App.vue b/examples/src/App.vue index 375e998..6230560 100644 --- a/examples/src/App.vue +++ b/examples/src/App.vue @@ -105,6 +105,35 @@ + 提示框: + + 成功提示 + 失败提示 + 警告提示 + + + + + 复制功能 + + copy固定内容 + + 复制渝过田晴 + + + 点击复制: {{ content }} + + + 手动点击copy + + + + + + + + + 实时预览tab组件 @@ -153,7 +182,18 @@ - + + + + + 自定义表单展示形式: + + + + 跳转一下 自定义右侧 + + + @@ -307,660 +347,660 @@ @@ -999,117 +1039,119 @@ h2 { padding-bottom: 10px; border-bottom: 1px solid #f0f0f0; } + .elementWrap { - /* width: 100%; */ - padding: 30px 40px; - background: #fff; + /* width: 100%; */ + padding: 30px 40px; + background: #fff; - .ele-item { - display: flex; - align-items: center; - justify-content: flex-start; - margin: 20px 0 30px; + .ele-item { + display: flex; + align-items: center; + justify-content: flex-start; + margin: 20px 0 30px; - label { - font-size: 16px; - font-weight: bold; - width: 100px; - margin-right: 10px; - text-align: left; - } + label { + font-size: 16px; + font-weight: bold; + width: 100px; + margin-right: 10px; + text-align: left; } + } } // 组合框样式 start // input drop组合使用 .combo-formItem { - ::v-deep { - .form-item-bottom { - position: relative; - } - - .select-trigger { - background: #F6F7FA; - border-color: transparent; - } + ::v-deep { + .form-item-bottom { + position: relative; + } - .is-open .select-trigger { - border-color: #006AFF; - } + .select-trigger { + background: #F6F7FA; + border-color: transparent; + } - .el-input__inner { - border-radius: 2px 0 0 2px; - } + .is-open .select-trigger { + border-color: #006AFF; } - .self-drop-wrap { - position: absolute; - z-index: 1; - width: 100%; + .el-input__inner { + border-radius: 2px 0 0 2px; } + } - .appendDrop { - height: 38px; - align-items: center; - border-radius: 0 2px 2px 0; - border: 1px solid #DFE2E6; - border-left-color: transparent; - justify-content: center; - box-sizing: border-box; - padding: 0 30px 0 12px; - text-overflow: ellipsis; - white-space: nowrap; - overflow: hidden; + .self-drop-wrap { + position: absolute; + z-index: 1; + width: 100%; + } - &:hover { - border: 1px solid #006AFF; - } + .appendDrop { + height: 38px; + align-items: center; + border-radius: 0 2px 2px 0; + border: 1px solid #DFE2E6; + border-left-color: transparent; + justify-content: center; + box-sizing: border-box; + padding: 0 30px 0 12px; + text-overflow: ellipsis; + white-space: nowrap; + overflow: hidden; + + &:hover { + border: 1px solid #006AFF; } + } } // 组合框样式 end .ScaleBox { - width: 1000px; - background-size: 100% 100%; - -ms-transition: 0.3s; - transition: 0.3s; - -ms-transform-origin: 0 0; - transform-origin: 0 0; - margin: 0 auto; + width: 1000px; + background-size: 100% 100%; + -ms-transition: 0.3s; + transition: 0.3s; + -ms-transform-origin: 0 0; + transform-origin: 0 0; + margin: 0 auto; } .cardfooter { - width: 100%; - bottom: 20px; - right: 30px; - text-align: right; - justify-content: center; + width: 100%; + bottom: 20px; + right: 30px; + text-align: right; + justify-content: center; } .btn1 { - cursor: pointer; - font-weight: bold; - width: 114px; - height: 40px; - /* 自动布局 */ - display: flex; - align-items: center; - padding: 11px 12px; - box-sizing: border-box; - color: #FFFFFF; - border-radius: 4px; - transition: all .3s; - background: linear-gradient(290deg, #FF4143 4%, #FF768B 92%); + cursor: pointer; + font-weight: bold; + width: 114px; + height: 40px; + /* 自动布局 */ + display: flex; + align-items: center; + padding: 11px 12px; + box-sizing: border-box; + color: #FFFFFF; + border-radius: 4px; + transition: all .3s; + background: linear-gradient(290deg, #FF4143 4%, #FF768B 92%); - &:hover { - opacity: 0.7; - background: linear-gradient(290deg, #FF4143 4%, #FF768B 92%); - } + &:hover { + opacity: 0.7; + background: linear-gradient(290deg, #FF4143 4%, #FF768B 92%); + } } -.btns{ - display: flex; - justify-content: flex-end; - align-items: center; + +.btns { + display: flex; + justify-content: flex-end; + align-items: center; } \ No newline at end of file diff --git a/packages/GuipFormItem/index.js b/packages/GuipFormItem/index.js new file mode 100644 index 0000000..bfe6152 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/packages/GuipFormItem/src/index.vue b/packages/GuipFormItem/src/index.vue new file mode 100644 index 0000000..50146bc --- /dev/null +++ b/packages/GuipFormItem/src/index.vue @@ -0,0 +1,52 @@ + + + + {{ label }} + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/GuipMessage/GuipMessage.vue b/packages/GuipMessage/GuipMessage.vue new file mode 100644 index 0000000..256bead --- /dev/null +++ b/packages/GuipMessage/GuipMessage.vue @@ -0,0 +1,170 @@ + + + + + + {{ message }} + + + + + + + \ No newline at end of file diff --git a/packages/GuipMessage/index.js b/packages/GuipMessage/index.js new file mode 100644 index 0000000..094216d --- /dev/null +++ b/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 } \ No newline at end of file diff --git a/packages/assets/dropDown_expand.png b/packages/assets/dropDown_expand.png index ec5192d..91553a3 100644 Binary files a/packages/assets/dropDown_expand.png and b/packages/assets/dropDown_expand.png differ diff --git a/packages/assets/message_Success.png b/packages/assets/message_Success.png new file mode 100644 index 0000000..786586d Binary files /dev/null and b/packages/assets/message_Success.png differ diff --git a/packages/assets/message_Warning.png b/packages/assets/message_Warning.png new file mode 100644 index 0000000..0a4c259 Binary files /dev/null and b/packages/assets/message_Warning.png differ diff --git a/packages/assets/message_error.png b/packages/assets/message_error.png new file mode 100644 index 0000000..194ba3c Binary files /dev/null and b/packages/assets/message_error.png differ diff --git a/packages/index.js b/packages/index.js index f9ed3b1..b8731ba 100644 --- a/packages/index.js +++ b/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 } \ No newline at end of file diff --git a/packages/styles/component.scss b/packages/styles/component.scss index 166c407..e878264 100644 --- a/packages/styles/component.scss +++ b/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; } -} \ No newline at end of file +} + +// 自定义下拉选择框组件 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 \ No newline at end of file diff --git a/packages/utils/clipboard.js b/packages/utils/clipboard.js new file mode 100644 index 0000000..4c81896 --- /dev/null +++ b/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} 是否复制成功 + */ +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} 是否复制成功 + */ +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; \ No newline at end of file diff --git a/packages/utils/common.js b/packages/utils/common.js new file mode 100644 index 0000000..f2af514 --- /dev/null +++ b/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 + "字符"; +} + diff --git a/packages/utils/dirClipBoard.js b/packages/utils/dirClipBoard.js new file mode 100644 index 0000000..73ddd76 --- /dev/null +++ b/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); + }); + } + }); + } +}; \ No newline at end of file diff --git a/packages/utils/eventBus.js b/packages/utils/eventBus.js new file mode 100644 index 0000000..2919ea8 --- /dev/null +++ b/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 \ No newline at end of file diff --git a/packages/utils/headerIcon.js b/packages/utils/headerIcon.js new file mode 100644 index 0000000..b1fe542 --- /dev/null +++ b/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', + } + }) + ]) + } + } + } \ No newline at end of file diff --git a/packages/utils/request.js b/packages/utils/request.js new file mode 100644 index 0000000..28c3458 --- /dev/null +++ b/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; \ No newline at end of file