20 KiB
20 KiB
Ciyon PC前端开发指南
框架概述
Ciyon是一个轻量级、高性能的PC前端开发框架,采用原生JavaScript开发,不依赖第三方库(如Vue、React、jQuery),专注于企业级后台管理系统和SaaS应用的快速开发。
核心特点
- 零依赖: 纯原生JavaScript,无第三方库依赖
- 组件化: 提供丰富的表单组件和业务组件
- 高性能: 优化的DOM操作和事件处理
- 响应式: 支持多端适配(PC、平板、手机)
- 国际化: 内置多语言支持
- 主题系统: CSS变量实现主题切换
框架架构
核心库文件
web/jscss/
├── ciy.js # 基础工具库和DOM操作
├── ciycmp.js # 表单组件库
├── ciycmp2.js # 扩展组件库
├── ciytable.js # 表格和列表组件
├── ciybigscreen.js # 数据大屏组件
├── ciy_websocket.js # WebSocket通信
├── style.css # 核心样式
└── theme.js # 暗黑模式
设计模式
DOM封装模式
框架使用 $5() 函数替代jQuery,提供统一的DOM操作接口:
// 选择元素
var dom = $5('.class-name');
// 链式调用
dom.css({color: 'red'}).addClass('active').show();
// 事件绑定
dom.on('click', function(e) {
console.log('clicked');
});
组件化模式
使用自定义标签和 ciycmp() 函数初始化组件:
<ciy-datetime com="starttime" value="2026-01-01"></ciy-datetime>
<script>
ciycmp({
dom: '[com=starttime]',
type: 'datetime',
onchange: function(e) {
console.log('selected:', e.value);
}
});
</script>
面向对象模式
使用类封装复杂功能:
var table = new ciyclass.table({
dom: '.table',
url: 'api/list',
pagecount: 20,
fn_done: function(json) {
console.log('data loaded');
}
});
table.callpage(1);
组件体系
表单组件
日期时间选择器(ciy-datetime)
<ciy-datetime com="date1" type="date" value="2026-01-01"></ciy-datetime>
<ciy-datetime com="datetime1" type="datetime" value="2026-01-01 12:00"></ciy-datetime>
<ciy-datetime com="month1" type="month" value="2026-01"></ciy-datetime>
属性说明:
com: 组件名称(必填)type: 类型(date/datetime/month)value: 初始值(时间戳或日期字符串)mindate: 最小日期maxdate: 最大日期placeholder: 占位文本
日期范围选择器(ciy-daterange)
<ciy-daterange com="daterange1" type="date" value="2026-01-01~2026-12-31"></ciy-daterange>
输出格式:开始日期~结束日期
下拉选择框(ciy-select)
<ciy-select com="status1" range="auditstatus" all="全部"></ciy-select>
<script>
ciycmp({
dom: '[com=status1]',
range: 'auditstatus', // 字典key
all: '全部', // 第一项
filter: {field: 'upid', value: 0}, // 过滤条件
onchange: function(e) {
console.log('selected:', e.value);
}
});
</script>
多选下拉框(ciy-selmulti)
<ciy-selmulti com="tags1" range="tags"></ciy-selmulti>
输出格式:,id1,id2,id3,
级联选择框(ciy-selcas)
<ciy-selcas com="region1" range="region"></ciy-selcas>
用于省市区级联选择。
开关(ciy-switch)
<ciy-switch com="enable1" value="1"></ciy-switch>
输出值:1(开启)/ 2(关闭)
单位编辑器(ciy-inputunitedit)
<ciy-inputunitedit com="unit1" value="瓶|24|盒|20|箱"></ciy-inputunitedit>
用于三级单位换算,如:1箱=20盒,1盒=24瓶。
地图选择器(ciy-map)
<ciy-map com="location1" value="116.404,39.915"></ciy-map>
输出经纬度:lng,lat
文本编辑器(ciy-textarea)
<ciy-textarea com="content1" max="1000"></ciy-textarea>
支持Tab键、字数统计、@用户提示。
文件上传(ciy-upload)
<ciy-upload com="file1" type="jpg,png" maxcount="1"></ciy-upload>
属性说明:
type: 允许的文件类型maxcount: 最大上传数量maxkb: 文件大小限制(KB)
列表组件
表格组件(ciyclass.table)
<div class="table">
<div class="list">
<!-- 表格内容自动生成 -->
</div>
<div>
<div class="btmbtn"></div>
<div>
<div class="btmbtn"></div>
<div class="page"></div>
</div>
</div>
</div>
<script>
var table = new ciyclass.table({
dom: '.table',
url: 'list',
pagecount: 20,
fn_tdcontent: function(key, value, field, row, json) {
// 自定义单元格内容
if (key == 'status') {
return '<span class="tag">' + value + '</span>';
}
// 自定义操作按钮
if (key == '_btn') {
var html = '';
html += `<a class="btn def" onclick="menubtn(this, 'view')">查看</a>`;
// 根据需求添加或删除按钮
// html += `<a class="btn" onclick="menubtn(this, 'edit')">修改</a>`;
return html;
}
return value;
},
fn_done: function(json, post) {
// 数据加载完成回调
}
});
table.callpage(1);
</script>
功能特性:
- 动态列配置
- 列宽调整记忆
- 列排序
- 列隐藏
- 行选择
- 分页
- 搜索
- 顶部选项卡筛选
顶部选项卡筛选:使用 fillsearch 的 lidata 参数添加顶部选项卡,liall 设置"全部"选项的文本:
table = new ciyclass.table({
dom: '.table',
url: 'list',
pagecount: 20,
fn_beforedata: function(json) {
ciyfn.fillsearch({
searchdom: '.search',
data: json,
liall: '全部',
lidata: [
{id: 1, name: '未使用'},
{id: 2, name: '已使用'}
],
//lidata: '【字典代码】', // 引用字典写法
//lidata: ':全部.1:未使用.2:已使用', //数组简写
liclick: function(dom) {
table.search(dom, 'li');
}
});
return json;
}
});
liid编号:自定义标识值应从1开始,不要使用0。
后端处理:在 setwhere 函数中根据 liid 参数进行筛选。
卡片列表(ciyclass.cardtable)
<div class="table">
<ul class="list row">
<!-- 卡片内容自动生成 -->
</ul>
<div>
<div class="btmbtn"></div>
<div class="page"></div>
</div>
</div>
<script>
var table = new ciyclass.cardtable({
dom: '.table',
url: 'api/list',
pagecount: 9,
fn_lihtml: function(ldat) {
return `<li data-id="${ldat.id}">
<div class="ciy-list">
<div class="l1">${ldat.name}</div>
<div class="l2">${ldat.memo}</div>
</div>
</li>`;
}
});
table.callpage(1);
</script>
功能组件
弹窗(ciyfn.alert)
ciyfn.alert({
title: '提示',
content: '操作成功',
btn: ['确定', '取消'],
cb: function(opn) {
if (opn.btn == '确定') {
// 确定操作
}
}
});
Toast提示(ciyfn.toast)
ciyfn.toast('操作成功');
ciyfn.toast('操作失败', 'error');
选项卡(ciyfn.tabcard)
<div class="ciy-tabcard">
<div class="tabs">
<div class="tab active">标签1</div>
<div class="tab">标签2</div>
</div>
<div class="contents">
<div class="content active">内容1</div>
<div class="content">内容2</div>
</div>
</div>
开发规范
HTML结构规范
标准页面结构
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link href="/jscss/style.css" rel="stylesheet">
<script src="/jscss/theme.js"></script>
</head>
<body>
<div class="container">
<!-- 搜索表单 -->
<form class="search" onsubmit="table.search(this,'btn');return false;">
<div>
<div class="sinps">
<div class="ciy-form">
<label>状态</label>
<div><ciy-select com="status" range="status"></ciy-select></div>
</div>
</div>
<div class="sbtns">
<button class="btn" type="submit">查询</button>
<a class="btn" onclick="edit(0)">添加</a>
</div>
</div>
</form>
<!-- 列表区域 -->
<div class="table">
<div class="list"></div>
<div>
<div class="btmbtn"></div>
<div class="page"></div>
</div>
</div>
</div>
<script src="/jscss/ciy.js"></script>
<script src="/jscss/ciycmp.js"></script>
<script src="/jscss/ciycmp2.js"></script>
<script src="/jscss/ciytable.js"></script>
<script src="./common.js"></script>
<script>
// 页面逻辑
</script>
</body>
</html>
表单结构
<div class="ciy-form">
<label>字段名称</label>
<div>
<ciy-select com="field" range="dict"></ciy-select>
</div>
</div>
JavaScript编码规范
页面初始化
'use strict';
var table;
ciyfn.pageload(function() {
// 初始化组件
ciycmp({
dom: '[com=status]',
range: 'status',
all: '全部'
});
// 初始化表格
table = new ciyclass.table({
dom: '.table',
url: 'api/list',
pagecount: 20
});
table.callpage(1);
});
组件初始化
// 标准初始化
ciycmp({
dom: '[com=component]',
range: 'dictionary',
value: '1',
onchange: function(e) {
console.log('changed:', e.value);
}
});
事件处理
// 使用$5绑定事件
$5('.btn-save').on('click', function(e) {
e.preventDefault();
// 保存逻辑
});
// 使用原生addEventListener
document.querySelector('.btn-save').addEventListener('click', function(e) {
e.preventDefault();
// 保存逻辑
});
API调用
ciyfn.callfunc('api/admin.update', {
id: 1,
name: 'test'
}, function(json) {
//返回code:1,成功回调
});
表单获取
var form = ciyfn.getform('.search');
console.log(form);
样式规范
CSS变量
:root {
/* 主色 */
--man3: #d7eeff;
--man4: #80c1f3;
--man5: #1E9FFF;
--man6: #1e89db;
--man7: #8568f7;
--mant: #ffffff;
/* 成功色 */
--succ5: #03a547;
--succ6: #048238;
--succt: #ffffff;
/* 警示色 */
--warn5: #e39725;
--warn6: #b97a1c;
--warnt: #ffffff;
/* 失败色 */
--dag5: #e34242;
--dag6: #bd2525;
--dagt: #ffffff;
/* 文字色 */
--txt1: #8c9ba4;
--txt2: #818e97;
--txt3: #738088;
--txt4: #646e76;
--txt5: #576067;
--txt6: #454d52;
--txt7: #2c3236;
--txt8: #060708;
--txt9: #000000;
/* 背景色 */
--bg1: #ffffff;
--bg2: #fbfbfc;
--bg3: #f7f8f8;
--bg4: #f0f2f2;
--bg5: #e3e6e7;
--bg6: #cdd2d4;
--bg7: #afb6b9;
--bg8: #939da1;
--bg9: #7e8a8e;
/* 其他css变量 */
--e-scroll: rgba(0, 0, 0, 0.2);
--e-tabselect: #fffec5;
--e-dialog: 2px 2px 20px -10px #000000;
--e-inputbg: #f7f7f7;
--e-inputbr: #ffffff;
--e-switchtxt: #2c3236;
--e-inputshadow: 0 1px 3px 0 #00000042;
--e-menusec: 0.5s;
}
响应式断点
@media (max-width: 767px) { /* 手机 */ }
@media (max-width: 991px) { /* 平板 */ }
@media (min-width: 576px) { /* 平板及以上 */ }
@media (min-width: 992px) { /* PC */ }
后端交互
API调用规范
标准请求格式
ciyfn.callfunc('/admin/user.update', {
param1: 'value1',
param2: 'value2'
}, function(json) {
// 处理响应
});
标准响应格式
{
"code": 1,
"msg": "成功",
"data": {},
"list": [],
"count": 100,
"pageno": 1,
"once": {}
}
认证机制
框架使用JWT进行用户认证,Token存储在Cookie或LocalStorage中。
// 获取
var me = ciyfn.getstorage(ciy_vars.tokenfield);
权限控制
// 检查权限
if (ciyfn.nopower(me.power, 'p1v')) {
ciyfn.alert('未被授权');
}
权限格式:.p1v.p2v.p3v.(p=父权限,v=查看)
最佳实践
组件命名
- 使用
com属性标识组件name - 组件名采用下划线命名法
- 表单字段使用有意义的名称
<ciy-select com="user_status" range="userstatus"></ciy-select>
<ciy-datetime com="start_time" type="datetime"></ciy-datetime>
数据字典
字典数据通过 range 属性引用:
// 字典结构
[{
id: 1,
name: '状态1',
upid: 0
}]
// 使用
ciycmp({
dom: '[com=status]',
range: 'userstatus' // 从localstorage缓存读取
});
事件处理
- 使用
onchange处理组件值变化 - 事件回调接收统一的参数对象
ciycmp({
dom: '[com=status]',
onchange: function(e) {
console.log(e.name); // 组件名称
console.log(e.value); // 组件值
console.log(e.dom); // DOM元素
console.log(e.from); // 触发来源
}
});
国际化
使用 ciyfn.lang() 函数实现多语言:
console.log(ciyfn.lang('保存'));
console.log(ciyfn.lang('删除'));
性能优化
- 使用
ciyfn.throttle()防抖 - 使用
ciyfn.lazyimg()懒加载图片 - 列表分页加载
- 图片URL转换:使用
ciyfn.file_stor()将数据库存储路径转换为云存储绝对URL - 图片查看:使用
ciyfn.showimg(index, imagesString)查看多张图片,参数为起始索引和用~连接的图片路径字符串
// 图片URL转换
var imgurl = ciyfn.file_stor('/2024/03/14/image.jpg');
// 查看多张图片,用~分隔(数据库中的原始存储)
ciyfn.showimg(1, '/img/1.jpg~/img/2.jpg~/img/3.jpg'); // 从第2张开始查看
// 防抖示例
$5('.search-input').on('input', function() {
if (ciyfn.throttle(this)) return;
// 搜索逻辑
});
开发流程
新建页面
- 在
web/xxx/下创建同名HTML文件和后端文件 - 引入必要的JS和CSS文件
- 使用组件标签构建页面
- 编写初始化逻辑
- 实现交互功能
- 测试和优化
示例代码
列表页面示例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="/jscss/style.css" rel="stylesheet">
</head>
<body>
<div class="container">
<form class="search" onsubmit="table.search(this,'btn');return false;">
<div>
<div class="sinps">
<div class="ciy-form">
<label>状态</label>
<div><ciy-select com="status" range="status" all="全部"></ciy-select></div>
</div>
</div>
<div class="sbtns">
<button class="btn" type="submit">查询</button>
<a class="btn" onclick="edit(0)">添加</a>
</div>
</div>
</form>
<div class="table">
<div class="list"></div>
<div>
<div class="btmbtn"></div>
<div class="page"></div>
</div>
</div>
</div>
<script src="/jscss/ciy.js"></script>
<script src="/jscss/ciycmp.js"></script>
<script src="/jscss/ciycmp2.js"></script>
<script src="/jscss/ciytable.js"></script>
<script src="./common.js"></script>
<script>
'use strict';
var table;
ciyfn.pageload(function() {
ciycmp({ dom: '[com=status]', range: 'status', all: '全部' });
table = new ciyclass.table({
dom: '.table',
url: 'list', //JS引擎将自动拼接为当前目录/文件名.list /admin/demopage.list
pagecount: 20,
fn_beforedata: function(json) {
ciyfn.fillsearch({ dom: '.search', data: json });
return json;
}
});
table.callpage(1);
});
function edit(id) {
ciyfn.alert({
title: id == 0 ? '添加' : '修改',
width: '600px',
frame: 'edit.html?id=' + id,
cb: function(opn) {
opn.close();
table.updateline(opn.inputs);
}
});
}
</script>
</body>
</html>
表单页面示例
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="/jscss/style.css" rel="stylesheet">
</head>
<body>
<div class="container">
<form onsubmit="save(event);">
<div class="ciy-form">
<label>名称</label>
<div><input type="text" name="name" required></div>
</div>
<div class="ciy-form">
<label>状态</label>
<div><ciy-switch com="status" value="1"></ciy-switch></div>
</div>
<div class="ciy-form">
<label>时间</label>
<div><ciy-datetime com="time" type="datetime"></ciy-datetime></div>
</div>
<button class="btn" type="submit">保存</button>
</form>
</div>
<script src="/jscss/ciy.js"></script>
<script src="/jscss/ciycmp.js"></script>
<script src="./common.js"></script>
<script>
'use strict';
ciyfn.pageload(function() {
ciycmp({ dom: '[com=status]' });
ciycmp({ dom: '[com=time]', type: 'datetime' });
});
function save(event) {
event.preventDefault();
var postparam = ciyfn.getform(event.target);
ciyfn.callfunc('save', postparam, function(json) {
ciyfn.toast('保存成功');
});
}
</script>
</body>
</html>
常用API
工具函数
// 全局函数
tostr(val, defval) // 转字符串
toint(val, def) // 转整数
tofloat(val, def) // 转浮点数
tostamp(time) // 转时间戳
isarray(v) // 判断是否Array类型
isobj(v) // 判断是否Object类型
iselement(v) // 判断是否Element类型
// DOM操作
$5(selector) // 选择元素
dom.css('color') // 获取样式
dom.css()['color'] // 获取计算后样式
dom.css(name, val) // 设置样式
dom.css({color:#xxx}) // 批量设置样式
dom.show() / dom.hide() // 显示/隐藏
dom.on(event, handler) // 绑定事件
dom.val(value) // 获取/设置值
// 数据处理
ciyfn.tojson(str) // JSON解析
ciyfn.jsontostr(obj) // JSON序列化
ciyfn.tostamp(time) // 转时间戳
ciyfn.todatetime(stamp, fmt) // 格式化日期
// 存储
ciyfn.getstorage(key) // 读取存储
ciyfn.setstorage(key, val) // 写入存储
// 消息提示
ciyfn.toast(msg, type) // 提示
ciyfn.alert(opn) // 弹窗
// API调用
ciyfn.callfunc(url, data, callback, opn) // 调用接口
表格API
// 分页
table.callpage(page); // 加载页面
table.updateline(json); // 更新行
table.delline(json); // 删除行
// 搜索
table.search(dom, act); // 执行搜索
八、注意事项
- 原生开发: 尽量使用原生开发,已封装$5(jQuery改进版)
- 严格模式: 所有JS代码使用
'use strict' - 引号使用: 代码中使用单引号
- 事件处理: 使用
$5().on()或原生addEventListener - 异步处理: 使用回调函数处理异步结果
- 数据验证: 后端必须验证数据
- 错误处理: 无需处理API错误,自动弹窗。特殊情况,在opn参数中定义fail函数
- 性能优化: 大数据列表使用分页加载
- 兼容性: 确保IE11+兼容
- 安全性: 防止XSS和CSRF攻击
- 菜单结构设计: PC端菜单应具备清晰的层级结构,二次菜单设计必须有一级菜单。
常见问题
Q: 组件不显示?
A: 检查是否正确引入了组件库文件(ciycmp.js、ciycmp2.js)。
Q: 表格数据不加载?
A: 检查API返回格式是否符合标准,确认 url 参数正确。
参考资料
- 组件示例:
web/admin/demo/front/ - API文档:参考各组件的源码注释
- 设计思路:参考框架注释中的设计说明
版本: 1.0.0
作者: Ciyon Team