gradient-string: 定制js控制台输出样式

本文介绍如何使用gradient-string库创建自定义的JavaScript控制台输出样式,包括Bash颜色标志,以及如何通过`console.log`实现动态文本颜色效果。

gradient-string: 定制js控制台输出样式

console本身可以控制输出样式,参考官方文档:https://developer.mozilla.org/zh-CN/docs/Web/API/Console

console.log('%c hello world ', 'background: #222; color: white');

在这里插入图片描述
添加 Bash 颜色标志来设置文本的颜色

// from create-vue
const banner = '\x1B[38;2;66;211;146mV\x1B[39m\x1B[38;2;66;211;146mu\x1B[39m\x1B[38;2;66;211;146me\x1B[39m\x1B[38;2;66;211;146m.\x1B[39m\x1B[38;2;66;211;146mj\x1B[39m\x1B[38;2;67;209;149ms\x1B[39m \x1B[38;2;68;206;152m-\x1B[39m \x1B[38;2;69;204;155mT\x1B[39m\x1B[38;2;70;201;158mh\x1B[39m\x1B[38;2;71;199;162me\x1B[39m \x1B[38;2;72;196;165mP\x1B[39m\x1B[38;2;73;194;168mr\x1B[39m\x1B[38;2;74;192;171mo\x1B[39m\x1B[38;2;75;189;174mg\x1B[39m\x1B[38;2;76;187;177mr\x1B[39m\x1B[38;2;77;184;180me\x1B[39m\x1B[38;2;78;182;183ms\x1B[39m\x1B[38;2;79;179;186ms\x1B[39m\x1B[38;2;80;177;190mi\x1B[39m\x1B[38;2;81;175;193mv\x1B[39m\x1B[38;2;82;172;196me\x1B[39m \x1B[38;2;83;170;199mJ\x1B[39m\x1B[38;2;83;167;202ma\x1B[39m\x1B[38;2;84;165;205mv\x1B[39m\x1B[38;2;85;162;208ma\x1B[39m\x1B[38;2;86;160;211mS\x1B[39m\x1B[38;2;87;158;215mc\x1B[39m\x1B[38;2;88;155;218mr\x1B[39m\x1B[38;2;89;153;221mi\x1B[39m\x1B[38;2;90;150;224mp\x1B[39m\x1B[38;2;91;148;227mt\x1B[39m \x1B[38;2;92;145;230mF\x1B[39m\x1B[38;2;93;143;233mr\x1B[39m\x1B[38;2;94;141;236ma\x1B[39m\x1B[38;2;95;138;239mm\x1B[39m\x1B[38;2;96;136;243me\x1B[39m\x1B[38;2;97;133;246mw\x1B[39m\x1B[38;2;98;131;249mo\x1B[39m\x1B[38;2;99;128;252mr\x1B[39m\x1B[38;2;100;126;255mk\x1B[39m'
console.log(`${banner}`)

// generated by the following code:
//
// require('gradient-string')([
//   { color: '#42d392', pos: 0 },
//   { color: '#42d392', pos: 0.1 },
//   { color: '#647eff', pos: 1 }
// ])('Vue.js - The Progressive JavaScript Framework'))

在这里插入图片描述
在这里插入图片描述
用于转换的库:gradient-string

<!DOCTYPE html> <html lang="zh" class="van-theme-light"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>每天按时喝水养生</title> <!-- Vant UI 样式 --> <link rel="stylesheet" href="assets/lib/index.css"> <!-- 自定义样式 --> <style> body { margin: 0; padding: 0; background: #e5e5e5; font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", Helvetica, Segoe UI, Arial, Roboto, "PingFang SC", "miui", "Hiragino Sans GB", "Microsoft Yahei", sans-serif; } .divbox { margin: 10px; } .flexbox { display: flex; flex-wrap: wrap; justify-content: center; gap: 10px; } .header { display: flex; } .caltitle { margin-top: 10px; cursor: pointer; } .tag_dev, .tag { color: #ed6a0c; background: linear-gradient(45deg, #ed6a0c, #ff976a); -webkit-background-clip: text; background-clip: text; color: transparent; text-align: center; font-size: large; font-weight: 700; } .o1, .ot1 { text-indent: -2em; padding-left: 2em; color: #009147; } .o2, .ot2 { text-indent: -2em; padding-left: 2em; } .o3, .ot3 { text-indent: -1.5em; padding-left: 1.5em; } .mrv { color: #009147; font-weight: 700; } .app { text-indent: -3em; padding-left: 3em; } .verTag { padding: 5px; border-radius: 5px; border: 1px solid; margin-left: 5px; } .verTag:hover { background-color: #1a73e8; color: #fff; } .audio { width: 100%; background-color: #f1f3f4; padding: 5px; border-radius: 5px; color: #c1c2c3; } #btnnn { background-color: #c1c2c3; color: #fff; } .clickble { cursor: pointer; } /* 动画样式 */ .ov-icon { display: inline-block; overflow: visible; vertical-align: -0.2em; } .ov-spin { animation: ov-spin 1s linear infinite; } .ov-wrench { animation: ov-wrench 2.5s ease infinite; } .ov-ring { animation: ov-ring 2s ease infinite; } .ov-pulse { animation: ov-pulse 2s linear infinite; } .ov-flash { animation: ov-flash 2s ease infinite; } .ov-float { animation: ov-float 2s linear infinite; } .ov-flip-horizontal { transform: scale(-1, 1); } .ov-flip-vertical { transform: scale(1, -1); } .ov-flip-both { transform: scale(-1, -1); } .ov-inverse { color: #fff; } /* 日历弹窗样式 */ .calendar-modal { position: fixed; top: 0; left: 0; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); display: flex; justify-content: center; align-items: center; z-index: 1000; } .calendar-container { background: white; border-radius: 12px; padding: 20px; width: 90%; max-width: 320px; box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15); } .calendar-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px; font-size: 18px; font-weight: bold; } .calendar-close { background: none; border: none; font-size: 24px; cursor: pointer; color: #999; width: 30px; height: 30px; border-radius: 50%; display: flex; align-items: center; justify-content: center; } .calendar-close:hover { background-color: #f5f5f5; } .calendar-grid { display: grid; grid-template-columns: repeat(7, 1fr); gap: 5px; text-align: center; } .calendar-day { padding: 8px 4px; border-radius: 6px; font-size: 14px; transition: background-color 0.2s; } .calendar-day.header { font-weight: bold; color: #666; cursor: default; } .calendar-day:hover { background-color: #f0f0f0; } .calendar-day.today { background-color: #1a73e8 !important; color: white !important; font-weight: bold; } .calendar-day.selected { background-color: #009147 !important; color: white !important; } @keyframes ov-spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } @keyframes ov-wrench { 0% { transform: rotate(-12deg); } 8% { transform: rotate(12deg); } 10%, 28%, 30%, 48%, 50%, 68% { transform: rotate(24deg); } 18%, 20%, 38%, 40%, 58%, 60% { transform: rotate(-24deg); } 75%, 100% { transform: rotate(0deg); } } @keyframes ov-ring { 0% { transform: rotate(-15deg); } 2% { transform: rotate(15deg); } 4%, 12% { transform: rotate(-18deg); } 6% { transform: rotate(18deg); } 8% { transform: rotate(-22deg); } 10% { transform: rotate(22deg); } 12% { transform: rotate(-18deg); } 14% { transform: rotate(18deg); } 16% { transform: rotate(-12deg); } 18% { transform: rotate(12deg); } 20%, 100% { transform: rotate(0deg); } } @keyframes ov-pulse { 0% { transform: scale(1.1); } 50% { transform: scale(0.8); } 100% { transform: scale(1.1); } } @keyframes ov-flash { 0%, 100%, 50% { opacity: 1; } 25%, 75% { opacity: 0; } } @keyframes ov-float { 0% { transform: translateY(-3px); } 50% { transform: translateY(3px); } 100% { transform: translateY(-3px); } } </style> </head> <body> <div id="app"> <!-- 导航栏 --> <div class="van-nav-bar van-hairline--bottom"> <div class="van-nav-bar__content"> <div class="van-nav-bar__left van-haptics-feedback"> <i class="van-icon van-icon-arrow-left van-nav-bar__arrow"></i> </div> <div class="van-nav-bar__title van-ellipsis">每天按时喝水养生</div> <div class="van-nav-bar__right van-haptics-feedback"> <i class="van-icon van-icon-wap-home-o" style="font-size: 32px;"></i> </div> </div> </div> <!-- 主要内容 --> <div class="divbox"> <div class="flexbox"> <!-- 左箭头按钮 --> <button type="button" class="van-button van-button--default van-button--normal" id="prevDay"> <div class="van-button__content"> <i class="van-icon van-icon-arrow-left van-button__icon"></i> </div> </button> <!-- 日期显示 --> <button type="button" class="van-button van-button--default van-button--normal" id="currentDateBtn"> <div class="van-button__content"> <span class="van-button__text" id="currentDate">2025/11/18(周二)</span> </div> </button> <!-- 右箭头按钮 --> <button type="button" class="van-button van-button--default van-button--normal" id="nextDay"> <div class="van-button__content"> <i class="van-icon van-icon-arrow van-button__icon"></i> </div> </button> </div> <!-- 分割线 --> <div class="van-divider van-divider--hairline" style="margin: 5px;"></div> <!-- 内容区域 --> <div> <!-- 这里可以添加具体的进度内容 --> </div> </div> <!-- 日历弹窗 --> <div id="calendarModal" class="calendar-modal" style="display: none;"> <div class="calendar-container"> <div class="calendar-header"> <span id="calendarMonth"></span> <button id="calendarClose" class="calendar-close">×</button> </div> <div id="calendarDays" class="calendar-grid"></div> </div> </div> <!-- CDN 引入 Vue 3 --> <script src="assets/lib/vue.global.js"></script> <!-- CDN 引入 Vant --> <script src="assets/lib/vant.min.js"></script> <link rel="stylesheet" href="assets/lib/mine.css" /> <!-- 底部占位 --> <div style="margin-bottom: 300px;"></div> <!-- 返回顶部按钮 --> <div class="van-back-top__placeholder"></div> </div> <script> document.addEventListener('DOMContentLoaded', function() { // ====== 关键:初始化 Vant 命令式调用环境 ====== const { createApp } = Vue; const app = createApp({}); // 创建空应用 app.use(vant); // 安装 Vant 插件 const tempDiv = document.createElement('div'); tempDiv.id = 'vant-init'; tempDiv.style.display = 'none'; document.body.appendChild(tempDiv); app.mount(tempDiv); // 挂载(必须!) // ============================================ const currentDateEl = document.getElementById('currentDate'); const currentDateBtn = document.getElementById('currentDateBtn'); let selectedDate = new Date(); function updateDateText(date) { const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; const y = date.getFullYear(); const m = String(date.getMonth() + 1).padStart(2, '0'); const d = String(date.getDate()).padStart(2, '0'); const w = weekdays[date.getDay()]; currentDateEl.textContent = `${y}/${m}/${d}(${w})`; } updateDateText(selectedDate); // 现在 vant.Calendar 已可用! currentDateBtn.addEventListener('click', () => { vant.Calendar({ show: true, defaultDate: selectedDate, showConfirm: false, onConfirm(date) { selectedDate = date; updateDateText(date); } }); }); document.getElementById('prevDay').addEventListener('click', () => { selectedDate.setDate(selectedDate.getDate() - 1); updateDateText(selectedDate); }); document.getElementById('nextDay').addEventListener('click', () => { selectedDate.setDate(selectedDate.getDate() + 1); updateDateText(selectedDate); }); }); </script> </body> </html> 我是直接通过离线html加载的vant 日历组件,其他功能正常,就是点击日期日历组件没反应弹不来,我需要创建vue应用和http server服务启动,只想通过点击html离线在浏览器加载。你帮我
11-19
<template> <div id="video_window" class="video_window"> <div class="header-wrapper"> <h3>{{props.deviceId}}——{{props.selectedCamera.name}}——Camera</h3> <el-button class="close-btn" circle @click="handleCloseAspect" ><el-icon><Close /></el-icon></el-button> </div> <el-form :model="formData" class="timepicker"> <el-form-item label="起始时间"> <el-date-picker v-model="formData.starttime" type="datetime" placeholder="请选择起始时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYYMMDDTHHmmss[Z]"></el-date-picker> </el-form-item> <el-form-item label="结束时间"> <el-date-picker v-model="formData.endtime" type="datetime" placeholder="请选择结束时间" format="YYYY-MM-DD HH:mm:ss" value-format="YYYYMMDDTHHmmss[Z]"></el-date-picker> </el-form-item> <el-form-item><el-button type="primary" :disabled="isCreated" @click="startPlay">播放</el-button></el-form-item> <el-form-item><el-button type="primary" :disabled="isDisabled" @click="stopPlay">停止</el-button></el-form-item> <el-form-item><el-button type="primary" :disabled="isDisabled" @click="pausePlay">{{ isPlaying ? "暂停":"继续" }}</el-button></el-form-item> </el-form> <div class="mainAspect"> <div v-if="videoLoading" class="custom-loading-layer"> <div class="loading-spinner"></div> <p>视频加载中...</p> </div> <video id="videoElement" class="videoElement" ref="flvPlayer" autoplay preload="metadata"></video> </div> </div> </template> <script setup> import flvjs from "flv.js" import { Close } from '@element-plus/icons-vue' import { computed, onMounted, ref, watch, onUnmounted,defineEmits,nextTick } from 'vue'; import { getActionCallBack,videoCloseCallBack } from '../api'; import {useStatusStore} from '../stores/videoCache' import { ElMessage } from "element-plus"; import dayjs from 'dayjs'; const props = defineProps({ deviceId: { type: String, required: true }, selectedCamera:{ type: Object, required: true } }); const emit= defineEmits(['close']) const videoCache=useStatusStore(); const originVideoPath='rtsp://admin:sess2025@' const flvPath=ref(null); const flvPlayer = ref(null); let player=null; const videoLoading=ref(false); const formData=ref({starttime:null,endtime:null}); const isDisabled=ref(true); const isPlaying=ref(true); const isCreated=ref(false); // watch( // ()=>props.selectedCamera, // async (newVal,oldVal) =>{ // console.log(newVal); // console.log(oldVal); // if (player) { // player.destroy(); // player=null; // videoLoading.value=false; // await closeOldVideoStream(oldVal.ip, oldVal.channelNo); // } // },{deep:true} // ) const handleCloseAspect=()=>{ emit('close'); } const fetchVideoStream=async (starttime,endtime)=>{ const res = await getActionCallBack(originVideoPath+String(props.selectedCamera.ip)+":554/streaming/tracks/"+String(props.selectedCamera.channelNo)+"01?starttime="+starttime+"^&endtime="+endtime); flvPath.value=res.data; console.log(flvPath.value); }; const closeVideoStream=async ()=>{ const queryTime=flvPath.value.split('callback')[1]; const res = await videoCloseCallBack(props.selectedCamera.ip,props.selectedCamera.channelNo,queryTime); console.log(res.data); }; // const closeOldVideoStream=async (ip, channelNo)=>{ // const res = await videoCloseCallBack(ip, channelNo); // console.log(res.data); // }; const initPlayer=()=>{ if(flvPath.value!==null){ if (flvjs.isSupported()) { let videoElement = document.getElementById('videoElement') player = flvjs.createPlayer({ type: 'flv', isLive: true, fluid: true, stashInitialSize: 128,// 减少首桢显示等待时长 url: flvPath.value, //url地址 }) player.attachMediaElement(videoElement) player.load() player.on(flvjs.Events.METADATA_ARRIVED,()=>{ videoLoading.value=false; }) player.play() } }else{ return; } }; const startPlay=async ()=>{ const now=dayjs().format('YYYYMMDDTHHmmss[Z]'); if(formData.value.starttime===null){ ElMessage.error("请选择起始时间!"); return null; }else if(formData.value.endtime===null){ ElMessage.error("请选择结束时间!!"); return null; }else if(formData.value.endtime>=now){ ElMessage.error("结束时间不能大于当前时间!") return null; }else if(formData.value.endtime<=formData.value.starttime){ ElMessage.error("结束时间不能小于开始时间!") return null; }else{ videoLoading.value=true; await fetchVideoStream(formData.value.starttime,formData.value.endtime); initPlayer(); isDisabled.value=false; isCreated.value=true; } } const stopPlay=()=>{ if(player){ closeVideoStream(); player.destroy(); player=null; isCreated.value=false; isDisabled.value=true; isPlaying.value=true; } } const pausePlay=()=>{ if(player){ if(!isPlaying.value){ console.log(isPlaying.value); player.play(); isPlaying.value=true; }else{ player.pause(); // 暂停播放 isPlaying.value=false; } } } onUnmounted(() => { if (player) { closeVideoStream(); player.destroy(); player=null; } }); </script> <style scoped> .video_window{ position: fixed; top: 15%; left: 30%; width: 49%; height: 70%; border-radius: 5%; background: rgba(255, 255, 255, 0.85); backdrop-filter: blur(12px); border-radius: 20px; box-shadow: 0 12px 40px rgba(100, 100, 150, 0.15), 0 0 0 1px rgba(255, 255, 255, 0.6) inset; overflow: hidden; transition: all 0.4s cubic-bezier(0.175, 0.885, 0.32, 1.1); } /* 标题栏 - 渐变背景与现代化按钮 */ .header-wrapper { padding: 18px 25px; background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); position: relative; box-shadow: 0 4px 12px rgba(0,0,0,0.08); } .header-wrapper h3 { margin: 0; color: white; font-weight: 500; font-size: 1.2rem; letter-spacing: 0.5px; text-shadow: 0 1px 2px rgba(0,0,0,0.1); } /* 时间选择表单 - 融入整体设计 */ .timepicker { padding: 15px 30px; background: rgba(245, 245, 255, 0.85); border-bottom: 1px solid rgba(210, 210, 255, 0.6); display: flex; gap: 20px; flex-wrap: wrap; justify-content: center; } .el-form-item { margin-bottom: 0; } .el-form-item label { color: #5a5a8a; font-weight: 500; } .el-date-editor, .el-button { border-radius: 12px; box-shadow: 0 2px 6px rgba(0, 0, 0, 0.05); } .el-button--primary { background: linear-gradient(135deg, #7f5af0 0%, #2cb67d 100%); border: none; transition: all 0.3s ease; } .el-button--primary:hover { transform: translateY(-2px); box-shadow: 0 4px 10px rgba(127, 90, 240, 0.3); } .mainAspect{ width: 100%; height: 82%; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); position: relative; overflow: hidden; border-radius: 0 0 28px 28px; } .videoElement{ width: 100%; height: 100%; transform-origin: center; object-fit: contain; transition: opacity 0.3s; } .close-btn { position: absolute; right: 10px; top: 10px; z-index: 20; background: rgba(255,255,255,0.2); border: none; transition: all 0.3s ease; box-shadow: 0 2px 5px rgba(0,0,0,0.1); } /*加载状态样式 */ .custom-loading-layer { position: absolute; top: 0; left: 0; width: 100%; height: 100%; display: flex; flex-direction: column; justify-content: center; align-items: center; z-index: 10; background: rgba(15, 12, 41, 0.85); backdrop-filter: blur(4px); color: #94a3b8; } .loading-spinner { width: 40px; height: 40px; border: 3px solid rgba(255,255,255,0.3); border-radius: 50%; border-top-color: gray; animation: spin 1s linear infinite; border-top-color: #2cb67d; border-left-color: rgba(124, 58, 237, 0.5); border-bottom-color: rgba(44, 182, 125, 0.3); border-right-color: rgba(124, 58, 237, 0.2); border-width: 4px; } @keyframes spin { to { transform: rotate(360deg); } } </style>我这个为什么推流成功但是flvjs播放不出来
10-23
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Activiti流程图设计器</title> <!-- 引入BPMN-JS的CSS --> <link rel="stylesheet" href="https://unpkg.com/bpmn-js@14.0.0/dist/assets/diagram-js.css"> <link rel="stylesheet" href="https://unpkg.com/bpmn-js@14.0.0/dist/assets/bpmn-js.css"> <link rel="stylesheet" href="https://unpkg.com/bpmn-js@14.0.0/dist/assets/bpmn-font/css/bpmn.css"> <style> * { box-sizing: border-box; margin: 0; padding: 0; } body, html { height: 100%; font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); color: #333; overflow: hidden; } #container { display: flex; flex-direction: column; height: 100vh; max-width: 100%; margin: 0 auto; box-shadow: 0 0 30px rgba(0, 0, 0, 0.1); } #header { background: linear-gradient(to right, #2c3e50, #4a6491); color: white; padding: 15px 20px; display: flex; justify-content: space-between; align-items: center; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.2); z-index: 10; } .logo { display: flex; align-items: center; gap: 15px; } .logo-icon { font-size: 28px; color: #3498db; } .header-title { font-size: 22px; font-weight: 600; } .header-subtitle { font-size: 14px; opacity: 0.8; margin-top: 3px; } .button-group { display: flex; gap: 12px; } .button { padding: 10px 20px; border: none; border-radius: 5px; font-weight: 600; cursor: pointer; display: flex; align-items: center; gap: 8px; transition: all 0.3s ease; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); } .button:hover { transform: translateY(-2px); box-shadow: 0 4px 8px rgba(0, 0, 0, 0.15); } .button:active { transform: translateY(0); } .button-save { background: linear-gradient(to right, #27ae60, #2ecc71); color: white; } .button-reset { background: linear-gradient(to right, #e74c3c, #c0392b); color: white; } .button-export { background: linear-gradient(to right, #3498db, #2980b9); color: white; } #canvas-container { flex: 1; position: relative; overflow: hidden; background: #f8f9fa; } #canvas { height: 100%; width: 100%; } #status-bar { background: #2c3e50; color: #ecf0f1; padding: 8px 20px; font-size: 14px; display: flex; justify-content: space-between; align-items: center; } .status-item { display: flex; align-items: center; gap: 8px; } .status-indicator { width: 10px; height: 10px; border-radius: 50%; background-color: #2ecc71; } .notification { position: fixed; top: 20px; right: 20px; padding: 15px 25px; border-radius: 5px; color: white; font-weight: 500; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2); z-index: 1000; opacity: 0; transform: translateX(100%); transition: all 0.4s ease; } .notification.show { opacity: 1; transform: translateX(0); } .notification.success { background: linear-gradient(to right, #27ae60, #2ecc71); } .notification.error { background: linear-gradient(to right, #e74c3c, #c0392b); } .notification.warning { background: linear-gradient(to right, #f39c12, #e67e22); } .sidebar { position: absolute; top: 0; right: 0; width: 300px; height: 100%; background: white; box-shadow: -5px 0 15px rgba(0, 0, 0, 0.1); transform: translateX(100%); transition: transform 0.3s ease; z-index: 5; padding: 20px; overflow-y: auto; } .sidebar.open { transform: translateX(0); } .sidebar h3 { margin-bottom: 15px; color: #2c3e50; border-bottom: 2px solid #3498db; padding-bottom: 10px; } .properties-group { margin-bottom: 20px; } .property-item { margin-bottom: 15px; } .property-item label { display: block; margin-bottom: 5px; font-weight: 500; color: #2c3e50; } .property-item input, .property-item textarea, .property-item select { width: 100%; padding: 8px 12px; border: 1px solid #ddd; border-radius: 4px; font-size: 14px; } .toggle-sidebar { position: absolute; top: 20px; right: 20px; background: #3498db; color: white; border: none; border-radius: 5px; padding: 8px 15px; cursor: pointer; z-index: 6; } .tools-palette { position: absolute; top: 20px; left: 20px; background: white; border-radius: 8px; box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); padding: 15px; z-index: 5; display: flex; flex-direction: column; gap: 10px; } .tool-btn { width: 40px; height: 40px; border-radius: 50%; display: flex; align-items: center; justify-content: center; background: #f8f9fa; border: 1px solid #e0e0e0; cursor: pointer; transition: all 0.2s; } .tool-btn:hover { background: #3498db; color: white; transform: scale(1.1); } .tool-btn.active { background: #3498db; color: white; } </style> </head> <body> <div id="container"> <div id="header"> <div class="logo"> <!-- <div class="logo-icon">📊</div> --> <div> <div class="header-title">流程图设计器</div> <!-- <div class="header-subtitle">基于BPMN.js的专业工作流设计工具</div> --> </div> </div> <div class="button-group"> <button id="save-button" class="button button-save"> <i>💾</i> 保存 </button> <!-- <button id="export-button" class="button button-export"> <i>📤</i> 导出XML </button> --> <button id="reset-button" class="button button-reset"> <i>🔄</i> 重置 </button> </div> </div> <div id="canvas-container"> <button id="toggle-sidebar" class="toggle-sidebar">属性面板</button> <div id="canvas"></div> <!-- <div class="tools-palette"> <div class="tool-btn active" title="选择工具">↖️</div> <div class="tool-btn" title="创建任务">📝</div> <div class="tool-btn" title="创建网关">⚖️</div> <div class="tool-btn" title="创建事件">🎯</div> </div> --> </div> <div id="status-bar"> <div class="status-item"> <div class="status-indicator"></div> <span>就绪</span> </div> <div class="status-item"> <span id="element-count">0 个元素</span> </div> <div class="status-item"> <span id="last-save">未保存</span> </div> </div> </div> <div id="notification" class="notification"></div> <div id="sidebar" class="sidebar"> <h3>元素属性</h3> <div class="properties-group"> <div class="property-item"> <label for="element-name">名称</label> <input type="text" id="element-name" placeholder="输入元素名称"> </div> <!-- <div class="property-item"> <label for="element-id">ID</label> <input type="text" id="element-id" readonly> </div> --> <div class="property-item"> <label for="element-desc">描述</label> <textarea id="element-desc" rows="3" placeholder="输入元素描述"></textarea> </div> <!-- <div class="property-item"> <label for="element-type">类型</label> <input type="text" id="element-type" readonly> </div> --> </div> <!-- <div class="properties-group"> <h4>执行属性</h4> <div class="property-item"> <label for="element-assignee">负责人</label> <input type="text" id="element-assignee" placeholder="输入负责人"> </div> <div class="property-item"> <label for="element-candidate">候选组</label> <input type="text" id="element-candidate" placeholder="输入候选组"> </div> <div class="property-item"> <label for="element-due">到期时间</label> <input type="text" id="element-due" placeholder="输入到期时间"> </div> </div> --> </div> <!-- 引入BPMN-JSJavaScript--> <script src="https://unpkg.com/bpmn-js@14.0.0/dist/bpmn-modeler.development.js"></script> <script> // 初始化BPMN设计器 let bpmnModeler = null; let isModelerReady = false; // DOM元素引用 const canvas = document.getElementById('canvas'); const saveButton = document.getElementById('save-button'); const resetButton = document.getElementById('reset-button'); // const exportButton = document.getElementById('export-button'); const toggleSidebar = document.getElementById('toggle-sidebar'); const sidebar = document.getElementById('sidebar'); const notification = document.getElementById('notification'); const statusIndicator = document.querySelector('.status-indicator'); const elementCount = document.getElementById('element-count'); const lastSave = document.getElementById('last-save'); // 初始化设计器 async function initializeModeler() { try { // 创建BPMN模型实例 bpmnModeler = new BpmnJS({ container: canvas, keyboard: { bindTo: document } }); // 监听模型就绪事件 bpmnModeler.on('import.done', function(event) { console.log('BPMN模型加载完成'); isModelerReady = true; updateStatusIndicator('ready'); // 更新元素计数 updateElementCount(); // 显示成功通知 showNotification('流程图加载完成!', 'success'); }); // 监听错误事件 bpmnModeler.on('error', function(err) { console.error('BPMN模型错误', err); isModelerReady = false; updateStatusIndicator('error'); showNotification(`模型错误: ${err.message}`, 'error'); }); // 监听元素变化事件 bpmnModeler.on('element.changed', function(event) { updateElementCount(); }); // 创建新流程图 await createNewDiagram(); } catch (err) { console.error('初始化BPMN模型失败', err); showNotification(`初始化失败: ${err.message}`, 'error'); } } // 创建新的流程图 async function createNewDiagram() { if (!bpmnModeler) return; try { updateStatusIndicator('loading'); await bpmnModeler.createDiagram(); console.log('创建流程图成功'); lastSave.textContent = '未保存'; showNotification('已创建新流程图', 'success'); } catch (err) { console.error('创建流程图失败', err); showNotification(`创建流程图失败: ${err.message}`, 'error'); } } // 保存流程图 - 解决saveXML未触发的问题 async function saveDiagram() { if (!bpmnModeler || !isModelerReady) { showNotification('模型尚未准备好,请稍后再试', 'warning'); return; } try { updateStatusIndicator('saving'); // 使用Promise方式调用saveXML解决未触发问题 const { xml } = await bpmnModeler.saveXML({ format: true }); console.log('BPMN XML:', xml); // 在实际应用中,这里可以发送到服务器保存 // await saveToServer(xml); // 更新状态 const now = new Date(); lastSave.textContent = `最后保存: ${now.toLocaleTimeString()}`; // 显示成功通知 showNotification('流程图保存成功!XML内容已输出控制台', 'success'); // 恢复状态 setTimeout(() => updateStatusIndicator('ready'), 1000); } catch (err) { console.error('保存失败', err); showNotification(`保存失败: ${err.message}`, 'error'); updateStatusIndicator('error'); } } // 导出XML async function exportXML() { if (!bpmnModeler || !isModelerReady) { showNotification('模型尚未准备好,请稍后再试', 'warning'); return; } try { const { xml } = await bpmnModeler.saveXML({ format: true }); // 创建下载链接 const blob = new Blob([xml], { type: 'application/xml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = 'activiti-process.bpmn'; document.body.appendChild(a); a.click(); // 清理 setTimeout(() => { document.body.removeChild(a); URL.revokeObjectURL(url); }, 100); showNotification('BPMN XML已导出', 'success'); } catch (err) { console.error('导出失败', err); showNotification(`导出失败: ${err.message}`, 'error'); } } // 重置流程图 function resetDiagram() { if (confirm('确定要重置吗?当前内容将丢失。')) { createNewDiagram(); } } // 更新状态指示器 function updateStatusIndicator(status) { switch (status) { case 'ready': statusIndicator.style.backgroundColor = '#2ecc71'; break; case 'loading': statusIndicator.style.backgroundColor = '#f39c12'; break; case 'saving': statusIndicator.style.backgroundColor = '#3498db'; break; case 'error': statusIndicator.style.backgroundColor = '#e74c3c'; break; default: statusIndicator.style.backgroundColor = '#95a5a6'; } } // 更新元素计数 function updateElementCount() { if (!bpmnModeler) return; try { const elementRegistry = bpmnModeler.get('elementRegistry'); const count = elementRegistry ? elementRegistry.getAll().length : 0; elementCount.textContent = `${count} 个元素`; } catch (err) { console.error('更新元素计数失败', err); } } // 显示通知 function showNotification(message, type) { notification.textContent = message; notification.className = `notification ${type} show`; setTimeout(() => { notification.classList.remove('show'); }, 3000); } // 页面加载完成后初始化 window.addEventListener('load', async function() { // 显示初始化通知 showNotification('正在初始化流程图设计器...', 'warning'); // 初始化设计器 await initializeModeler(); // 绑定按钮事件 saveButton.addEventListener('click', saveDiagram); resetButton.addEventListener('click', resetDiagram); // exportButton.addEventListener('click', exportXML); toggleSidebar.addEventListener('click', function() { sidebar.classList.toggle('open'); toggleSidebar.textContent = sidebar.classList.contains('open') ? '关闭面板' : '属性面板'; }); }); </script> </body> </html> 改造成在vue2中能使用
07-19
<template> <view class="distribution-detail"> <!-- 导航栏 --> <wd-navbar :showBack="true" title="分销业绩详情" title-style="font-weight: 600;" background="#ffffff" border-bottom="true" /> <!-- 业绩列表 --> <scroll-view class="detail-list" scroll-y> <!-- 业绩统计卡片 --> <view class="stat-card" v-if="detailList.length > 0 && !isLoading"> <view class="stat-item"> <view class="stat-label">累计业绩</view> <view class="stat-value">{{ totalAmount }} 元</view> </view> <view class="stat-item"> <view class="stat-label">有效记录</view> <view class="stat-value">{{ totalCount }} 条</view> </view> <view class="stat-item"> <view class="stat-label">最近更新</view> <view class="stat-value">{{ lastUpdateTime }}</view> </view> </view> <!-- 业绩列表项 --> <view class="detail-item" v-for="(item, index) in detailList" :key="item.id"> <view class="item-left"> <!-- 修复:使用处理后的remark字段 --> <view class="user-tag"> <uni-icons type="person" size="20" color="#409eff" class="tag-icon" /> <view class="tag-text">{{ item.formattedRemark }}</view> </view> <view class="info-title"> <text>{{ getLevelText(item.level) }}</text> <text class="title-divider">|</text> <text>业绩记录</text> </view> </view> <view class="item-right"> <view class="info-content">¥{{ item.amount || '0.00' }}</view> <view class="settlement-tag" :class="getSettlementClass(item.settlementStatus)"> {{ getSettlementText(item.settlementStatus) }} </view> </view> <view class="item-footer"> <view class="item-time"> <uni-icons type="clock" size="22" color="#888" class="time-icon" /> {{ formatTime(item.createTime) || '暂无时间' }} </view> <view class="item-id"> 记录ID:<text class="id-text">{{ item.id || '无' }}</text> </view> </view> <view class="divider" v-if="index !== detailList.length - 1"></view> </view> <!-- 空状态 --> <view v-if="detailList.length === 0 && !isLoading" class="empty-state"> <uni-icons type="creditcard" size="70" color="#c0c4cc" class="empty-icon" /> <view class="empty-text">暂无业绩记录</view> <view class="empty-desc">您的分销业绩将在这里实时展示</view> </view> <!-- 加载状态 --> <view v-if="isLoading" class="loading-state"> <uni-icons type="spinner" size="36" color="#409eff" animation="spin" /> <view class="loading-text">加载中...</view> </view> </scroll-view> </view> </template> <script lang="ts" setup> import { getPageDistributionListByparentId } from '@/api/business/mobile/appApi' import { ref } from 'vue'; import { onLoad, onReachBottom } from '@dcloudio/uni-app' // 响应式数据 const detailList = ref<any[]>([]) const isLoading = ref(true) const page = ref(1) const limit = ref(20) const targetUserId = ref('') const totalCount = ref(0) const totalAmount = ref('0.00') const lastUpdateTime = ref('暂无') // 格式化时间 const formatTime = (timeStr: string) => { if (!timeStr) return ''; return timeStr.replace(/-/g, '.').substring(0, 16) // 2025.09.12 11:41 } // 业绩等级文本 const getLevelText = (level: number) => { const levelMap: Record<number, string> = { 0: '一级分销', 1: '二级分销', 2: '三级分销' } return levelMap[level] || '普通分销' } // 结算状态文本 const getSettlementText = (status: number) => { const statusMap: Record<number, string> = { 0: '待结算', 1: '已结算', 2: '已取消' } return statusMap[status] || '未知状态' } // 结算状态样式 const getSettlementClass = (status: number) => { const classMap: Record<number, string> = { 0: 'status-pending', 1: 'status-completed', 2: 'status-canceled' } return classMap[status] || 'status-default' } // 格式化业绩记录 const formatRecords = (records: any[]) => { return records.map(item => ({ ...item, // 处理备注信息:提取昵称部分 formattedRemark: item.remark ? item.remark.split(':').pop().trim() || '分销用户' : '分销用户', // 确保金额格式正确 amount: parseFloat(item.amount || 0).toFixed(2) })) } // 更新统计数据 const updateStats = () => { // 计算总金额 const total = detailList.value.reduce((sum, item) => { return sum + parseFloat(item.amount) }, 0) totalAmount.value = total.toFixed(2) // 更新最近时间 if (detailList.value.length > 0) { const sorted = [...detailList.value].sort((a, b) => new Date(b.createTime).getTime() - new Date(a.createTime).getTime() ) lastUpdateTime.value = formatTime(sorted[0].createTime) } } // 获取业绩数据 const getDetailList = (init: boolean = false) => { if (init) { page.value = 1 detailList.value = [] isLoading.value = true } if (!targetUserId.value) { console.error('缺少targetUserId参数') isLoading.value = false uni.showToast({ title: '加载失败:缺少用户ID', icon: 'none' }) return } getPageDistributionListByparentId( page.value, limit.value, targetUserId.value ).then(res => { console.log('接口响应', res) // 修复1:正确处理响应结构 if (res.code === 200) { const resData = res.data || {} // 修复2:验证数据有效性 if (!resData.records) { throw new Error('records字段缺失') } const records = formatRecords(resData.records) // 更新列表 detailList.value = init ? records : [...detailList.value, ...records] // 更新统计信息 totalCount.value = resData.total || 0 updateStats() // 分页处理 if (resData.current < resData.pages) { page.value += 1 } } else { // 修复3:使用顶层msg字段 uni.showToast({ title: `加载失败: ${res.msg || '未知错误'}`, icon: 'none', duration: 3000 }) } }).catch(err => { console.error('请求失败', err) uni.showToast({ title: '数据加载失败,请重试', icon: 'error', duration: 2000 }) }).finally(() => { isLoading.value = false }) } // 页面加载 onLoad((options) => { if (options?.targetUserId) { targetUserId.value = options.targetUserId getDetailList(true) } else { uni.showToast({ title: '缺少用户ID参数', icon: 'error' }) } }) // 触底加载 onReachBottom(() => { if (!isLoading.value && detailList.value.length < totalCount.value) { getDetailList() } }) </script> <style scoped> .distribution-detail { min-height: 100vh; background-color: #f7f8fa; } .detail-list { height: calc(100vh - 100rpx); padding: 20rpx 30rpx; box-sizing: border-box; } .stat-card { background: linear-gradient(135deg, #409eff 0%, #66b1ff 100%); border-radius: 16rpx; padding: 30rpx; margin-bottom: 30rpx; color: #ffffff; display: flex; justify-content: space-between; box-shadow: 0 8rpx 20rpx rgba(64, 158, 255, 0.15); } .stat-item { display: flex; flex-direction: column; align-items: center; width: 33.33%; } .stat-label { font-size: 26rpx; opacity: 0.9; margin-bottom: 10rpx; } .stat-value { font-size: 36rpx; font-weight: 600; } .detail-item { background-color: #ffffff; border-radius: 16rpx; padding: 30rpx; margin-bottom: 24rpx; box-shadow: 0 4rpx 12rpx rgba(0, 0, 0, 0.05); } .item-left { margin-bottom: 20rpx; } .user-tag { display: flex; align-items: center; color: #409eff; font-size: 26rpx; margin-bottom: 12rpx; } .tag-icon { margin-right: 8rpx; } .info-title { font-size: 32rpx; color: #303133; font-weight: 500; } .title-divider { color: #c0c4cc; margin: 0 12rpx; } .item-right { display: flex; justify-content: space-between; align-items: center; margin-bottom: 24rpx; } .info-content { font-size: 38rpx; color: #e63946; font-weight: 600; } .settlement-tag { padding: 6rpx 16rpx; border-radius: 20rpx; font-size: 24rpx; font-weight: 500; } .status-pending { background-color: #e6f7ff; color: #1890ff; } .status-completed { background-color: #f0fff4; color: #52c41a; } .status-canceled { background-color: #f5f5f5; color: #999; } .item-footer { display: flex; justify-content: space-between; align-items: center; font-size: 24rpx; color: #888; } .item-time { display: flex; align-items: center; } .time-icon { margin-right: 8rpx; } .item-id { color: #999; } .id-text { color: #666; font-family: monospace; } .divider { height: 2rpx; background-color: #f5f7fa; margin-top: 24rpx; } .empty-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding-top: 250rpx; gap: 24rpx; z-index: 10; position: relative; } .empty-icon { margin-bottom: 16rpx; } .empty-text { font-size: 34rpx; color: #606266; font-weight: 500; } .empty-desc { font-size: 26rpx; color: #909399; } .loading-state { display: flex; flex-direction: column; align-items: center; justify-content: center; padding: 100rpx 0; gap: 20rpx; } .loading-text { font-size: 28rpx; color: #409eff; } </style> 为什么没有展示 Request URL: https://fenxiao.studyease.cn/prod-api/fen/distributionRecord/pageDistributionRecords?pageNum=1&pageSize=20&targetUserId=12452903 {"code":200,"msg":"Operation successful","data":{"records":[{"id":"1966467227383889922","appId":3,"promoterId":12345,"targetUserId":12452903,"amount":"2.43","level":0,"settlementStatus":0,"settlementAt":null,"isDelete":0,"calculate":"1","createTime":"2025-09-12 11:41:37","updateTime":"2025-09-12 11:49:58","deleteTime":0,"remark":"昵称:兔兔很饿"}],"total":1,"current":1,"size":20,"last":true,"pages":1}} 下面的信息而是报里一个加载失败,未知错误?
09-16
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值