This commit is contained in:
boi 2025-07-08 04:50:17 +08:00
parent d13d3f7642
commit ade688f804
19 changed files with 1755 additions and 472 deletions

1
.gitignore vendored
View File

@ -14,6 +14,7 @@
# Dependency directories (remove the comment below to include it)
资料/*
web/ud/docs/*
web/ud/2025/*
web/ud/2026/*
web/ud/2027/*

View File

@ -0,0 +1,141 @@
<!DOCTYPE html>
<html>
<head>
<title></title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<link href="/jscss/style.css" rel="stylesheet" type="text/css" />
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/github-markdown-css/5.2.0/github-markdown.min.css">
<script type="text/javascript" charset="utf-8" src="/jscss/theme.js"></script>
<style>
</style>
</head>
<body>
<div class="container">
<form>
<div class="char5 row">
<div class="ciy-form col-24 col-sm-12">
<label class="lang imp">smtp服务器</label>
<div>
<input type="text" name="smtpserver" style="width:100%;" />
</div>
</div>
<div class="ciy-form col-24 col-sm-12">
<label class="lang imp">smtp端口</label>
<div>
<input type="text" name="smtpport" style="width:100%;" />
</div>
</div>
<div class="ciy-form col-24 col-sm-12">
<label class="lang imp">邮箱地址</label>
<div>
<input type="text" name="user" style="width:100%;" />
</div>
</div>
<div class="ciy-form col-24 col-sm-12">
<label class="lang imp">安全密码</label>
<div>
<input type="text" name="pass" style="width:100%;" />
</div>
</div>
<div class="ciy-form col-24 col-sm-12">
<label class="lang imp">发送地址</label>
<div>
<input type="text" name="mailto" style="width:100%;" />
</div>
</div>
<div class="ciy-form col-24 col-sm-12">
<label class="lang imp">标题</label>
<div>
<input type="text" name="title" style="width:100%;" />
</div>
</div>
<div class="ciy-form col-24 col-sm-24 top">
<label class="lang imp">内容</label>
<div>
<ciy-textarea com="content" minheight="15em"></ciy-textarea>
</div>
</div>
</div>
<div class="txt-center">
<a onclick="mailsend(this)" class="lang btn lg">发送邮件</a>
</div>
</form>
<form>
<div class="char5 row">
<div class="ciy-form col-24 col-sm-12">
<label class="lang imp">imap服务器</label>
<div>
<input type="text" name="imapserver" style="width:100%;" />
</div>
</div>
<div class="ciy-form col-24 col-sm-12">
<label class="lang">LastUID</label>
<div>
<input type="text" name="lastuid" style="width:100%;" />
</div>
</div>
<div class="ciy-form col-24 col-sm-12">
<label class="lang imp">邮箱地址</label>
<div>
<input type="text" name="user" style="width:100%;" />
</div>
</div>
<div class="ciy-form col-24 col-sm-12">
<label class="lang imp">安全密码</label>
<div>
<input type="text" name="pass" style="width:100%;" />
</div>
</div>
</div>
<div class="txt-center">
<a onclick="mailrecv(this)" class="lang btn lg">接收邮件</a>
</div>
</form>
</div>
<script type="text/javascript" src="/jscss/ciy.js"></script>
<script type="text/javascript" src="/jscss/ciycmp.js"></script>
<script type="text/javascript" src="../../common.js"></script>
<script type="text/javascript">
'use strict';
var Glob = {};
ciyfn.pageload(function () {
var val = ciyfn.getstorage('_testmailsend', {});
$5('[name=smtpserver]').val(val.smtpserver);
$5('[name=smtpport]').val(val.smtpport);
$5('[name=user]').val(val.user);
$5('[name=pass]').val(val.pass);
$5('[name=mailto]').val(val.mailto);
$5('[name=title]').val(val.title);
ciycmp({ dom: $5('[com=content]'), autoheight: true, value: val.content });
val = ciyfn.getstorage('_testmailrecv', {});
$5('[name=imapserver]').val(val.imapserver);
$5('[name=user]').val(val.user);
$5('[name=pass]').val(val.pass);
$5('[name=lastuid]').val(val.lastuid);
});
function mailsend(dom) {
var postparam = ciyfn.getform(dom);
ciyfn.setstorage('_testmailsend', postparam);
if (ciyfn.throttle(dom)) return;
ciyfn.callfunc("mailsend", postparam, function (json) {
ciyfn.toast('发送成功');
});
}
function mailrecv(dom) {
var postparam = ciyfn.getform(dom);
ciyfn.setstorage('_testmailrecv', postparam);
if (ciyfn.throttle(dom)) return;
ciyfn.callfunc("mailrecv", postparam, function (json) {
console.log(json);
ciyfn.toast('接收' + json.mails.length + '封邮件最新UID' + json.maxuid);
});
}
</script>
</body>
</html>

View File

@ -0,0 +1,36 @@
<?php
namespace web\admin\demo\dyn;
class mail {
public static function json_mailsend() {
$post = new \ciy\post();
$mail = new \ciy\smtp();
$mail->smtp_server = $post->get('smtpserver');
$mail->smtp_port = $post->getint('smtpport');
$mail->user = $post->get('user');
$mail->pass = $post->get('pass');
$param = array();
$param['mailto'] = $post->get('mailto');
$param['title'] = $post->get('title');
$param['content'] = $post->get('content','','all');
$retmail = $mail->sendmail($param);
clog($mail->log);
if(is_string($retmail))
return errjson($retmail);
return succjson();
}
public static function json_mailrecv() {
$post = new \ciy\post();
$mail = new \ciy\imap();
$mail->imap_server = $post->get('imapserver');
$mail->user = $post->get('user');
$mail->pass = $post->get('pass');
$lastuid = $post->getint('lastuid');
$retmail = $mail->recvmail($lastuid);
clog($mail->log);
if(is_string($retmail))
return errjson($retmail);
return succjson($retmail);
}
}

View File

@ -159,6 +159,10 @@
<a class="btn sm" href="demo_proxy.html" target="_blank">proxy prop</a>
Proxy响应式示例。
</div>
<div>
<a class="btn sm" href="dyn/mail.html" target="_blank">mail</a>
Mail收发邮件。
</div>
<div>
<a class="btn sm" href="dyn/openai.html" target="_blank">deepseek</a>
DeepSeek接口。

View File

@ -0,0 +1,75 @@
const CiyTreemenu = {
name: 'ciy-treemenu',
template: `
<div class="treemenu">
<ul>
<li v-for="item in treeData" :key="item.id">
<span v-if="item.children" @click="item.expanded = !item.expanded" style="font-weight:bold;">{{ item.name }}</span>
<span v-else @click="selectNode(item)" :style="{color:item.select?'#0068ac':''}">{{ item.name }}</span>
<div v-if="item.children && item.expanded" class="tree-children">
<ciy-treemenu :tree-data="item.children" @node-selected="selectNode"></ciy-treemenu>
</div>
</li>
</ul>
</div>
`,
props: {
treeData: {
type: Array,
required: true,
default: () => []
}
},
beforeCreate() {
console.log('beforeCreate: 实例初始化之后,数据观测和事件配置之前被调用');
},
created() {
console.log('created: 实例创建完成后被调用,已完成数据观测、属性和方法的运算',this.treeData);
},
beforeMount() {
console.log('beforeMount: 挂载开始之前被调用相关的render函数首次被调用');
},
mounted() {
console.log('mounted: 实例挂载完成后调用此时DOM已经渲染', this.$el);
},
beforeUpdate() {
console.log('beforeUpdate: 数据更新时调用发生在虚拟DOM重新渲染和打补丁之前');
},
updated() {
console.log('updated: 数据更改导致的虚拟DOM重新渲染和打补丁后调用');
},
beforeUnmount() {
console.log('beforeUnmount: 实例销毁之前调用,此时实例仍然完全可用');
},
unmounted() {
console.log('unmounted: 实例销毁后调用,所有指令都已解绑,所有事件监听器都已移除');
},
activated() {
console.log('activated: 被keep-alive缓存的组件激活时调用');
},
deactivated() {
console.log('deactivated: 被keep-alive缓存的组件停用时调用');
},
errorCaptured(err, instance, info) {
console.log('errorCaptured: 捕获一个来自后代组件的错误时被调用');
console.error('Error:', err);
console.log('Vue instance:', instance);
console.log('Error info:', info);
// 可以阻止错误继续向上传播
return false;
},
renderTracked({ key, target, type }) {
// console.log('renderTracked: 跟踪虚拟DOM重新渲染时调用');
// console.log(`操作类型: ${type}, 目标: ${target}, 键: ${key}`);
},
renderTriggered({ key, target, type }) {
// console.log('renderTriggered: 虚拟DOM重新渲染被触发时调用');
// console.log(`操作类型: ${type}, 目标: ${target}, 键: ${key}`);
},
methods: {
selectNode(node) {
this.$emit('node-selected', node);
}
}
};

220
web/docs/index.html Normal file
View File

@ -0,0 +1,220 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Ciyon Docs - 众产全栈开发框架</title>
<meta name="keywords" content="全栈开发框架,Golang开发框架,PHP开发框架,移动端开发框架,Vue3开发框架" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=0, shrink-to-fit=no" />
<link rel="stylesheet" href="/jscss/style.css" />
<link rel="stylesheet" href="/jscss/web.css" />
<script src="/jscss/theme.js"></script>
<style>
.treemenu ul {
list-style-type: none;
}
.treemenu li {
cursor: pointer;
padding: 0.5em 0;
margin-left: 1em;
}
.menu {
min-width: 15em;
padding-right: 2em;
overflow: overlay;
transition: all 0.3s ease;
}
.menubtn {
display: none;
bottom: -0.6em;
left: -0.3em;
font-size: 3em;
}
@media (max-width: 767px) {
.menu.close {
min-width: 0;
width: 0;
padding-right: 0;
}
.menubtn {
display: block;
}
}
</style>
</head>
<body>
<div id="app" style="display:flex;flex-direction:column;height:100vh;">
<header class="rel">
<div style="padding: 1em 2em;font-size: 1.2em;">Ciyon Docs - 众产全栈开发框架</div>
<div v-if="!mbmenu" @click="mbmenu = !mbmenu" class="menubtn abs">💠</div>
</header>
<div style="height:1px;background: linear-gradient(90deg, #99999922, #999999, #99999922);"></div>
<div style="display:flex;flex:1;min-height:0;">
<div class="menu" :class="{ 'close': !mbmenu }">
<ciy-treemenu :tree-data="menuData" @node-selected="onNodeSelected"></ciy-treemenu>
</div>
<div style="width:1px;background: linear-gradient(0deg, transparent, #999999, #99999944);"></div>
<main style="flex:1;overflow: overlay;padding:0 0.5em;max-width: 60em;">
<div v-if="helpdata" v-html="helpdata">
</div>
</main>
</div>
<footer class="hidden-dn-sm">
<div>© 2018-2026 众产®. All Rights Reserved.</div>
<div>
<a href="https://ciy.cn/" target="_blank" class="un">众产</a> 
<a href="https://ciy.cn/code/" target="_blank" class="un">众产IT工程</a> 
<a href="https://ciy.cn/tc/" target="_blank" class="un">众产区块链</a>
</div>
<div>众产(杭州)科技有限公司</div>
<div>
<a href="https://beian.miit.gov.cn" target="_blank" nofollow>浙ICP备19004547号-1</a>
</div>
</footer>
<footer class="hidden-up-sm">
<div>
<a href="https://ciy.cn/" target="_blank" class="un">众产</a> 
<a href="https://ciy.cn/code/" target="_blank" class="un">众产IT工程</a> 
<a href="https://ciy.cn/tc/" target="_blank" class="un">众产区块链</a>
</div>
</footer>
</div>
<div style="position: fixed; z-index:7;right:0;bottom:0;">
<div title="转到白天模式" class="mode-light" onclick="ciy_chgtheme(true)"></div>
<div title="转到暗夜模式" class="mode-dark" onclick="ciy_chgtheme(false)"></div>
<a title="返回顶部" class="btn-totop" onclick="gotop()"></a>
</div>
<div class="bgbtm"></div>
<script src="/jscss/vue3.js"></script>
<script src="components/treemenu.js"></script>
<script src="/jscss/ciy.js"></script>
<script src="/jscss/ciycmp2.js"></script>
<script src="/jscss/web.js"></script>
<script>
var ciy_vars = {
"storlist": { '/': '/ud/' }
};
function createVueApp() {
const app = Vue.createApp({
components: {
'ciy-treemenu': CiyTreemenu
},
data() {
console.log('2. data() - 初始化数据');
return {
mbmenu: false,
helpdata: '',
menuData: []
};
},
beforeCreate() {
console.log('1. beforeCreate() - 实例初始化之后,数据观测和事件配置之前');
},
created() {
console.log('3. created() - 实例创建完成后被立即调用');
//console.log(`此时可以访问数据: count = ${this.count}`);
},
beforeMount() {
console.log('4. beforeMount() - 挂载开始之前被调用');
},
mounted() {
console.log('5. mounted() - 实例被挂载后调用此时可以访问DOM元素');
ciyfn.callfunc('init', {}, json => {
this.menuData = conv_treedata(json.menu, 0, 0);
ciyfn.ajax({
url: '/ud/docs/1_' + json.menu[0].uptimes + '.txt', method: 'GET', success: txt => {
this.helpdata = ciyfn.markdown(txt);
}
});
});
},
beforeUpdate() {
console.log('6. beforeUpdate() - 数据更新时调用发生在虚拟DOM打补丁之前');
//console.log(`当前count值: ${this.count}`);
},
updated() {
console.log('7. updated() - 由于数据更改导致的虚拟DOM重新渲染和打补丁后调用');
//console.log(`更新后count值: ${this.count}`);
},
beforeUnmount() {
console.log('8. beforeUnmount() - 实例销毁之前调用');
},
unmounted() {
console.log('9. unmounted() - 实例销毁后调用');
},
errorCaptured(err, vm, info) {
console.log('errorCaptured() - 捕获子孙组件的错误时调用');
console.error(`错误: ${err.toString()}\n信息: ${info}`);
// 可以返回false阻止错误继续向上传播
return true;
},
renderTracked({ key, target, type }) {
// console.log('renderTracked() - 跟踪虚拟DOM重新渲染时调用');
// console.log(`键: ${key}, 目标: ${target}, 类型: ${type}`);
},
renderTriggered({ key, target, type }) {
// console.log('renderTriggered() - 触发虚拟DOM重新渲染时调用');
// console.log(`键: ${key}, 目标: ${target}, 类型: ${type}`);
},
methods: {
onNodeSelected(node) {
if (this.selectnode)
this.selectnode.select = false;
this.selectnode = node;
this.mbmenu = false;
if (node.uptimes == 0)
return this.helpdata = '<div style="padding:1em;color:var(--dag5);">文档' + node.id + '未开始录入</div>';
ciyfn.ajax({
url: '/ud/docs/' + node.id + '_' + node.uptimes + '.txt', method: 'GET', success: txt => {
node.select = true;
this.helpdata = ciyfn.markdown(txt);
gotop();
}, fail: () => {
this.helpdata = '<div style="padding:1em;color:var(--dag5);">文档' + node.id + '不存在</div>';
}
});
}
}
});
app.mount('#app');
}
function conv_treedata(rows, rootid) {
var ret = [];
var cnt = rows.length;
for (var i = 0; i < cnt; i++) {
if (rows[i].id < 10)
continue;
if (rows[i].upid != rootid)
continue;
var tree = {};
tree.id = rows[i]['id'];
tree.name = rows[i]['name'];
tree.uptimes = rows[i]['uptimes'];
tree.expanded = true;
var childtree = conv_treedata(rows, rows[i]['id']);
if (childtree.length > 0)
tree.children = childtree;
ret.push(tree);
}
return ret;
}
function gotop() {
document.querySelector('main').scrollTo({
top: 0,
behavior: 'smooth'
});
}
document.addEventListener('DOMContentLoaded', createVueApp);
</script>
</body>
</html>

15
web/docs/index.php Normal file
View File

@ -0,0 +1,15 @@
<?php
namespace web\docs;
class index {
public static function json_init() {
global $db;
$csql = new \ciy\sql('doc_help');
$csql->where('isuse', 1)->order('csort desc,id');
$ret['menu'] = $db->get($csql);
if ($ret['menu'] === false)
return errjson('菜单获取失败:' . $db->error);
return succjson($ret);
}
}

356
web/docs/manage.html Normal file
View File

@ -0,0 +1,356 @@
<!DOCTYPE html>
<html>
<head>
<title>文档简易管理</title>
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1">
<meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
<link href="/jscss/style.css" rel="stylesheet" type="text/css" />
<script type="text/javascript" charset="utf-8" src="/jscss/theme.js"></script>
</head>
<body>
<div class="container">
<form class="search" onsubmit="search(this);return false;">
<div>
<div class="sinps"></div>
<div class="sbtns">
<button class="lang btn" type="submit">查询</button>
<a class="lang btn" onclick="multiadd()">批量添加</a>
<a class="lang btn" onclick="datatxt()">文档结构</a>
<!-- <a class="lang btn dag" onclick="deltree()">批量删除</a> -->
<input type="password" name="pant" />
</div>
</div>
</form>
<div class='table'>
<div class="loading">Loading...</div>
<div class="list"></div>
</div>
</div>
<div id="alert_modifyupid" style="display:none;">
<div class="ciy-form">
<label class="lang" style="min-width:3em;">调整到</label>
<div>
<ciy-select com="newupid"></select>
</div>
</div>
</div>
<div id="alert_multiadd" style="display:none;">
<div class="ciy-form">
<label class="lang" style="min-width:3em;">添加到</label>
<div>
<ciy-select com="upid"></select>
</div>
</div>
<div class="ciy-form v">
<label style="min-width:3em;"><span class="lang">名称</span><code class="lang">(一行一个)</code></label>
<div>
<ciy-textarea com="multi" minheight="30em"></ciy-textarea>
</div>
</div>
</div>
<div id="alert_edit" style="display:none;">
<div class="char4 row">
<div class="col-24">
<ciy-markdown com="content" imgwidth="1000" />
</div>
</div>
</div>
<script type="text/javascript" src="/jscss/ciy.js"></script>
<script type="text/javascript" src="/jscss/ciycmp.js"></script>
<script type="text/javascript" src="/jscss/ciycmp2.js"></script>
<script type="text/javascript" src="/jscss/ciytable.js"></script>
<script type="text/javascript">
'use strict';
var ciy_vars = {
"uploadurl": "docs/upload"
, "dupsec": 60
, "storselect": '/'
, "storlist": { '/': '/ud/', 'A': 'https://cf5.nyyzsoft.cn/ud/', 'Z': 'https://cf5.nyyzsoft.cn/ud/' }
};
var table;
var Glob = {};
ciyfn.pageload(function () {
Glob.ciysvg = '<svg viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg"><path class="svgfill" d="M987.136 384.512C919.552 131.584 668.16-22.528 415.232 27.136c219.648-41.984 434.176 79.36 488.96 283.136 57.344 214.016-82.944 437.76-313.344 499.2-49.664 13.312-78.848 64-65.536 113.152 13.312 49.664 64 78.848 113.152 65.536 263.168-70.144 418.816-340.48 348.672-603.648zM116.224 713.216C58.88 499.2 199.68 275.456 430.08 214.016c49.664-13.312 78.848-64 65.536-113.152C482.816 51.2 432.128 21.504 382.464 34.816c-263.68 70.656-419.84 340.992-349.184 604.16 67.584 252.928 319.488 407.04 571.392 356.864-219.648 42.496-433.664-78.848-488.448-282.624z m93.184-203.776L291.84 449.536c44.544-32.256 90.112-32.256 133.12-0.512l83.456 60.928-149.504-230.4-149.504 229.888z m516.096-60.416L808.96 509.952l-149.504-229.888-149.504 229.376 82.432-59.904c44.544-32.256 90.112-32.256 133.12-0.512z m-366.08 291.84L441.856 680.96c44.544-32.256 90.112-32.256 133.12-0.512l83.456 60.928-149.504-230.4-149.504 229.888z"></path></svg>';
$5(document).on('keydown', function (e) {
if (e.ctrlKey && e.keyCode == 83) {//Ctrl+S
e.preventDefault();
var upbtns = $5('.upbtn:not(.def)');
upbtns.each(function (dom) {
menubtn(dom, 'edit');
}, 0.5);
}
});
table = new ciyclass.table({
dom: '.table'
, url: 'init'
, thfield: function (name, key, field, json) {
if (key == 'name')
return '<a style="font-size: 1.5em;vertical-align: middle;padding-right: 0.5em;" onclick="exall()"></a> ' + ciyfn.lang(name);
}
, fn_beforedata: function (json) {
json.list = ciyfn.conv_treerow(json.list, 0, 0);
ciyfn.fillsearch({
searchdom: '.search'
, data: json
});
return json;
}
, fn_trprop: function (data, json) {
var rettxt = ' data-upid="' + data['upid'] + '"';
if (data['_count'] == '0')
rettxt += ' data-search="ok"';
// if (data['_deep'] > 0)
// rettxt += ' style="display:none;"';
return rettxt;
}
, fn_tdcontent: function (key, datashow, field, data) {
if (key == 'isuse') {
return '<ciy-switch com="' + key + '" value="' + data[key] + '" y="公开" n="内部" />';
}
if (key == '_btn') {
var html = '<a class="lang btn def upbtn" onclick="menubtn(this, \'edit\')">' + (data['id'] == 0 ? '新增' : '更新') + '</a>';
html += '<a class="lang btn" onclick="menubtn(this, \'tz\')">调整</a>';
if (data['_count'] == 0)
html += '<a class="lang btn succ" onclick="menubtn(this, \'doc\')">Doc</a>';
return html;
}
if (key == 'csort') {
return '<input spellcheck="false" style="width:100%;" onkeydown="btnset(this)" type="text" name="' + key + '" value="' + datashow.replace(/"/g, '&quot;').split('~')[0] + '"/>';
}
if (key == 'name') {
var deep = toint(data['_deep']);
var html = ' '.repeat(deep);
if (data['_count'] > 0) {
html += '<span class="ciy-tree-dot"></span> <input style="margin-left: 0.3em;width:100%;" onclick="event.stopPropagation();" onkeydown="btnset(this)" type="text" name="' + key + '" value="' + data['name'] + '"/>';
} else {
if (deep > 0)
html += '  ';
html += '<input style="width:100%;" onclick="event.stopPropagation();" onkeydown="btnset(this)" type="text" name="' + key + '" value="' + data['name'] + '"/>';
}
return {
divprop: (data['_count'] > 0 ? ' data-treeid="' + data['id'] + '"' : '') + ' data-deep="' + data['_deep'] + '" style="display: flex;" class="ciy-tree-spread"'// class="ciy-tree-spread"
, datashow: html
};
}
}
, fn_done: function () {
table.tree();
var doms = $5('[com]');
for (var i = 0; i < doms.length; i++) {
ciycmp({
dom: doms[i], onchange: function (e) {
if (e.from == 'init') return;
btnset(e.dom);
}
});
}
}
});
table.callpage(1);
});
function exall() {
$5('div[data-deep="0"]').trigger('click');
}
function search(dom) {
Glob.key = ciyfn.getform(dom);
$5('[data-search]').each(function (rdom) {
var bshow = true;
for (var k in Glob.key) {
if (!Glob.key[k])
continue;
var val = $5('[name=' + k + ']', rdom).val();
if (val.indexOf(Glob.key[k]) == -1) {
bshow = false;
break;
}
}
rdom.style.display = bshow ? '' : 'none';
});
}
function btnset(dom) {
var domtr = $5(dom).parent('tr');
$5('.btn.upbtn', domtr).removeClass('def');
}
function multiadd() {
ciyfn.alert({
title: '添加'
, width: 'mb'
, contentstyle: 'overflow:hidden;'
, content: document.getElementById("alert_multiadd").innerHTML
, fn_showed: function (doc, dom) {
ciycmp({ dom: $5('[com="upid"]', dom), range: fillrange(), value: 0 });
ciycmp({ dom: $5('[com="multi"]', dom) });
}
, btns: ["添加", "*关闭"]
, cb: function (opn) {
if (opn.btn == "关闭")
return opn.close();
if (ciyfn.throttle(opn.dombtn)) return;
opn.inputs.pant = $5('[name=pant]').val();
ciyfn.callfunc('multiadd', opn.inputs, function (json) {
ciyfn.toast('操作成功', function () {
window.location.reload();
});
});
}
});
}
function datatxt() {
var tdata = new Array();
for (var i in table.data) {
if (table.data[i].isuse != 1)
continue;
tdata.push(table.data[i]);
}
tdata.sort((a, b) => toint(b.csort) - toint(a.csort));
function deeptxt(tdata, upid = 0, indent = '') {
var menutxt = '';
for (var i in tdata) {
if (tdata[i].upid != upid)
continue;
menutxt += indent + tdata[i].name + '\n';
if (tdata[i].upid === upid)
menutxt += deeptxt(tdata, tdata[i].id, indent + ' ');
}
return menutxt;
}
ciyfn.alert({
title: '文档结构'
, width: 'mb'
, height: 'pc'
, content: '<textarea style="height: 100%;">' + deeptxt(tdata) + '</textarea>'
});
}
function menubtn(dom, btn) {
var domtr = $5(dom).parent('tr');
var id = toint(domtr.attr('data-id'));
if (btn == 'edit') {
var postparam = ciyfn.getform(dom, 'TR');
postparam.id = id;
postparam.upid = table.data[id].upid;
postparam.cbid = table.once.cbid;
postparam.pant = $5('[name=pant]').val();
ciyfn.callfunc('update', postparam, function (json) {
$5(dom).addClass('def');
$5('[name=url]', domtr).val(json.url);
$5('[name=demo]', domtr).val(json.demo);
$5('[name=pow]', domtr).val(json.pow);
});
}
if (btn == 'tz') {
ciyfn.alert({
title: '调整菜单项层级'
, content: document.getElementById("alert_modifyupid").innerHTML
, fn_showed: function (doc, dom) {
if (id == 0)
return;
ciycmp({ dom: $5('[com="newupid"]', dom), range: fillrange(), value: table.data[id].upid });
}
, btns: ["调整", "*关闭"]
, cb: function (opn) {
if (opn.btn == "关闭")
return opn.close();
opn.inputs.id = id;
opn.inputs.pant = $5('[name=pant]').val();
if (ciyfn.throttle(opn.dombtn)) return;
ciyfn.callfunc('modifyupid', opn.inputs, function (json) {
window.location.reload();
});
}
});
}
if (btn == 'doc') {
ciyfn.alert({
title: table.data[id].name
, width: 'max'
, content: document.getElementById("alert_edit").innerHTML
//, noparent:true
, fn_showed: function (doc, dom) {
Glob.edit = id;
ciyfn.ajax({
url: '/ud/docs/' + id + '_' + table.data[id].uptimes + '.txt', method: 'GET', success: txt => {
ciycmp({ dom: $5('[com=content]', dom), width: 'pc', path: '{Y}/{m}{d}/docs', value: txt });
}, fail: () => {
ciycmp({ dom: $5('[com=content]', dom), width: 'pc', path: '{Y}/{m}{d}/docs', value: '' });
}
});
}
, btns: ["提交", "*关闭"]
, cb: function (opn) {
if (opn.btn == "关闭")
return opn.close();
opn.inputs.id = id;
opn.inputs.pant = $5('[name=pant]').val();
if (ciyfn.throttle(opn.dombtn)) return;
ciyfn.callfunc('uptxt', opn.inputs, function (json) {
table.data[id].uptimes = json.uptimes;
table.updateline(json);
opn.close();
});
}
});
}
}
function fillrange() {
var tdata = [];
for (var i in table.data)
tdata.push(table.data[i]);
tdata.sort((a, b) => toint(b.csort) - toint(a.csort));
var ranges = [];
ranges.push({ id: 0, name: '顶层' });
deephtml(tdata, 0, 1, 5);
function deephtml(tdata, upid, deep, maxdeep) {
if (deep > maxdeep)
return;
for (var i in tdata) {
if (tdata[i].upid != upid)
continue;
ranges.push({ id: tdata[i].id, name: ' '.repeat(deep) + ciyfn.lang(tdata[i].name) });
deephtml(tdata, tdata[i].id, deep + 1, maxdeep);
}
}
return ranges;
}
function deltree() {
var array = [];
$5('[data-id]', '.table').each(function (dom) {
if (!$5(dom).hasClass("selected"))
return;
var deep = toint($5('[data-deep]', dom).attr("data-deep"));
array.push({ id: $5(dom).attr("data-id"), deep: deep });
});
if (array.length == 0)
return ciyfn.toast("请至少选择一条信息");
ciyfn.alert('已选<span class="txt-lggg px1">' + array.length + '</span>条,确认批量删除?', function (opn) {
opn.close();
if (opn.btn == "关闭")
return;
function deldeep(deep) {
if (deep < 0)
return;
var ids = [];
for (var j = 0; j < array.length; j++) {
if (array[j].deep == deep) {
ids.push(array[j].id);
}
}
if (ids.length == 0)
return deldeep(deep - 1);
var postparam = {};
postparam.ids = ids.join(",");
ciyfn.callfunc('del', postparam, function (json) {
table.delline(json);
deldeep(deep - 1);
});
}
deldeep(5);
}, { btns: ["删除", "*关闭"] });
}
</script>
</body>
</html>

226
web/docs/manage.php Normal file
View File

@ -0,0 +1,226 @@
<?php
namespace web\docs;
class manage {
static function verify($pant) {
$cfg = webini('mock');
if($pant == $cfg['docpass'])
return true;
return false;
}
public static function json_init() {
global $db;
$post = new \ciy\post();
$query = $post->get('query');
$csql = new \ciy\sql('doc_help');
$csql->order('csort desc,id');
$rows = $db->get($csql);
$ret = array('list' => $rows);
if ($post->getbool('field')) {
$field = array();
$fshow = $db->getfield($field, 'doc_help');
$fshow = fieldadd($fshow, $field, -1, '_btn', '操作');
$field['csort']['thwidth'] = '5em';
$ret['field'] = $field;
$ret['fshow'] = $fshow;
}
if ($post->getbool('once')) {
$ret['once'] = array();
$input = array();
$input[] = array(
'type' => 'input',
'form' => 'name',
'name' => '标题',
'prop' => ' style="width:8em;"'
);
$ret['once']['input'] = $input;
}
return succjson($ret);
}
public static function json_update() {
global $db;
$post = new \ciy\post();
if (!self::verify($post->get('pant')))
return errjson('您未被授权操作');
$updata = array();
$id = $post->getint('id');
if($id < 10)
return errjson('不能操作');
$name = $post->get('name');
if ($name == '')
return errjson('请填写标题');
$csort = $post->getint('csort');
$isuse = $post->getint('isuse');
$csql = new \ciy\sql('doc_help');
$csql->where('id', $id);
$datarow = $db->getone($csql);
if (!is_array($datarow))
return errjson('数据不存在');
try {
$db->begin();
$updata = array();
$updata['name'] = $name;
$updata['isuse'] = $isuse;
$updata['csort'] = $csort;
$csql = new \ciy\sql('doc_help');
$csql->where('id', $id);
if ($db->update($csql, $updata) === false)
throw new \Exception('更新失败:' . $db->error);
$updata['id'] = $id;
$db->commit();
} catch (\Exception $ex) {
$db->rollback();
return errjson($ex->getMessage());
}
return succjson();
}
public static function json_del() {
global $db;
$rsuser = verifyfast();
return errjson('功能暂未开放');
if (nopower($db, $rsuser['id'], 'p903d'))
return errjson('您未被授权操作');
$post = new \ciy\post();
$ids = $post->get('ids');
if (empty($ids))
return errjson('请选择至少一条');
$csql = new \ciy\sql('doc_help');
$csql->where('id in', $ids);
$rows = $db->get($csql);
$vids = array();
try {
$db->begin();
foreach ($rows as $row) {
$delid = $row['id'];
if ($delid >= 10) {
delcheck($db, $delid, 'doc_help', 'upid', '子菜单');
}
delme($db, $delid, 'doc_help');
savelogdb($db, $rsuser['id'], 'doc_help', $row, null);
$vids[] = $delid;
}
$db->commit();
} catch (\Exception $ex) {
$db->rollback();
return errjson($ex->getMessage());
}
$ret['ids'] = $vids;
return succjson($ret);
}
public static function json_modifyupid() {
global $db;
$post = new \ciy\post();
if (!self::verify($post->get('pant')))
return errjson('您未被授权操作');
$id = $post->getint('id');
if($id < 10)
return errjson('不能操作');
$newupid = $post->getint('newupid');
$csql = new \ciy\sql('doc_help');
$csql->where('id', $id);
$datarow = $db->getone($csql);
if (!is_array($datarow))
return errjson('数据不存在');
try {
$db->begin();
$updata = array();
$updata['upid'] = $newupid;
$csql = new \ciy\sql('doc_help');
$csql->where('id', $id);
if ($db->update($csql, $updata) === false)
throw new \Exception('更新id失败:' . $db->error);
$updata['id'] = $id;
$db->commit();
} catch (\Exception $ex) {
$db->rollback();
return errjson($ex->getMessage());
}
return succjson();
}
public static function json_multiadd() {
global $db;
$post = new \ciy\post();
if (!self::verify($post->get('pant')))
return errjson('您未被授权操作');
$upid = $post->getint('upid');
$multi = explode("\n", $post->get('multi'));
$cnt = 0;
try {
$db->begin();
foreach ($multi as $m) {
if (empty(trim($m)))
continue;
$ms = explode('~', $m);
$name = trim($ms[0]);
if (empty($name))
continue;
$updata = array();
$updata['name'] = $name;
if (count($ms) > 1)
$updata['url'] = trim($ms[1]);
if (count($ms) > 2)
$updata['pow'] = trim($ms[2]);
$updata['isuse'] = 1;
$updata['upid'] = $upid;
$updata['csort'] = 10;
$updata['uptimes'] = 0;
$csql = new \ciy\sql('doc_help');
if ($db->insert($csql, $updata) === false)
throw new \Exception('操作数据库失败.' . $db->error);
$updata['id'] = $db->insert_id();
$cnt++;
}
$db->commit();
} catch (\Exception $ex) {
$db->rollback();
return errjson($ex->getMessage());
}
if ($cnt == 0)
return errjson('没有任何新增');
$updata = array();
$updata['url'] = '';
$updata['pow'] = '';
$csql = new \ciy\sql('doc_help');
$csql->where('id', $upid);
$db->update($csql, $updata);
return succjson();
}
public static function json_uptxt() {
global $db;
$post = new \ciy\post();
if (!self::verify($post->get('pant')))
return errjson('您未被授权操作');
$updata = array();
$id = $post->getint('id');
$content = $post->get('content','','all');
$csql = new \ciy\sql('doc_help');
$csql->where('id', $id);
$datarow = $db->getone($csql);
if (!is_array($datarow))
return errjson('数据不存在');
$uptimes = time();
try {
$db->begin();
$updata = array();
$updata['uptimes'] = $uptimes;
$csql = new \ciy\sql('doc_help');
$csql->where('id', $id);
if ($db->update($csql, $updata) === false)
throw new \Exception('更新失败:' . $db->error);
$updata['id'] = $id;
$db->commit();
} catch (\Exception $ex) {
$db->rollback();
return errjson($ex->getMessage());
}
file_put_contents(PATH_WEB . 'ud/docs/' . $id . '_' . $uptimes . '.txt', $content);
$ret['uptimes'] = $uptimes;
return succjson($ret);
}
}

125
web/docs/upload.php Normal file
View File

@ -0,0 +1,125 @@
<?php
/* =================================================================================
* License: GPL-2.0 license
* Author: 众产® https://ciy.cn/code
* Version: 0.1.2
====================================================================================*/
namespace web\docs;
class upload {
public static function json_upload() {
error_reporting(E_ALL ^ E_NOTICE);
$uploadcfg['exts'] = array(
'jpe', 'jpg', 'jpeg', 'gif', 'png', 'ai', 'bmp', 'psb', 'psd', 'tif', 'svg', 'webp',
'zip', '7z', 'rar', 'tar', 'arj', 'iso', 'cab', 'gz', 'glb',
'txt', 'csv', 'doc', 'docx', 'pps', 'ppt', 'pptx', 'pdf', 'wps', 'wpt', 'xls', 'xlsx', 'xml', 'et', 'ett',
'avi', 'mp4', 'mp3', 'swf', 'flv', 'f4v', 'm4v', 'wma', 'rm', 'rmvb', '3gp', 'ts', 'mts', 'vob', 'mpg', 'mpeg', 'mov', 'wmv', 'wav',
'bak', 'cad', 'chm', 'log', 'ai', 'ico'
);
$uploadcfg['noexts'] = array('goc', 'php', 'php3', 'php4', 'phtm', 'phtml', 'php5', 'js', 'html', 'htm', 'sh', 'so'); //建议nginx部署使用。
$uploadcfg['checkext'] = 'exts'; //扩展名白名单exts、黑名单noexts
$path = get('pathfile');
$rep = get('rep');
if(empty($path))
return errjson('缺少参数pathfile');
if (count($_FILES) == 0)
return errjson('没有文件上传');
$file = reset($_FILES);
if ($file['error'] > 0)
return errjson(\ciy\upload::UploadError($file['error']));
list($name, $extfile) = \ciy\upload::Fileext($path);
if ($uploadcfg['checkext'] == 'exts') {
if (!in_array($extfile, $uploadcfg['exts']))
return errjson("不允许上传{$extfile}类型文件");
} else {
if (in_array($extfile, $uploadcfg['noexts']))
return errjson("禁止上传{$extfile}类型文件");
}
$ret = \ciy\upload::SaveUploadFile($path, $file, $rep == 'true');
if (is_array($ret))
return succjson($ret);
return errjson($ret);
}
public static function json_yunsync() {
if (count($_FILES) == 0)
return errjson('没有文件上传');
$file = reset($_FILES);
if ($file['error'] > 0)
return errjson('上传参数出错:' . $file['error']);
//上传临时文件,通过http put 上传到s3
$post = new \ciy\post();
$headers = json_decode($post->get('headers'), true);
$url = $post->get('url');
$http = new \ciy\http();
foreach($headers as $key => $value){
$http->set_header($key,$value);
}
$http->set_method('PUT');
$http->upfile($url, $file['tmp_name']);
$statcode = $http->get_statcode();
if($statcode == 200)
return succjson();
$data = $http->get_data();
$ind = strpos($data,'error:');
if($ind !== false)
return errjson(substr($data, $ind + 6));
return errjson('code[' . $statcode . ']');
}
public static function json_s3() {
//根据token的ABC决定用哪个key包含access/key/region/bucket/endpoint
//存到目录和文件由js决定ud/xxx/xxx.jpg
$path = get('pathfile'); // s0/2/2024/0913/demo/65631_7101.jpg
$storselect = get('storselect'); // A
$cfg = webini('s3' . $storselect);
if(is_string($cfg))
return errjson($cfg);
$objectKey = 'ud/' . $path;
$sha256 = 'UNSIGNED-PAYLOAD';
$zdate = gmdate('Ymd\THis\Z');
$shortDate = substr($zdate, 0, 8);
$dateKey = hash_hmac('sha256', $shortDate, 'AWS4' . $cfg['secret'], true);
$regionKey = hash_hmac('sha256', $cfg['region'], $dateKey, true);
$serviceKey = hash_hmac('sha256', 's3', $regionKey, true);
$signingKey = hash_hmac('sha256', 'aws4_request', $serviceKey, true);
$canonicalUri = '/' . $cfg['bucket'] . '/' . $objectKey;
$canonicalQueryString = '';
$canonicalHeaders = 'host:' . $cfg['endpoint'] . "\n" .
'x-amz-acl:' . $cfg['acl'] . "\n" .
'x-amz-content-sha256:' . $sha256 . "\n" .
'x-amz-date:' . $zdate . "\n";
$signedHeaders = 'host;x-amz-acl;x-amz-content-sha256;x-amz-date';
$canonicalRequest = 'PUT' . "\n" .
$canonicalUri . "\n" .
$canonicalQueryString . "\n" .
$canonicalHeaders . "\n" .
$signedHeaders . "\n" .
$sha256;
$stringToSign = 'AWS4-HMAC-SHA256' . "\n" .
$zdate . "\n" .
$shortDate . '/' . $cfg['region'] . '/s3/aws4_request' . "\n" .
hash('sha256', $canonicalRequest);
$signature = hash_hmac('sha256', $stringToSign, $signingKey);
$authorizationHeader = 'AWS4-HMAC-SHA256 Credential=' . $cfg['access'] . '/' . $shortDate . '/' . $cfg['region'] . '/s3/aws4_request, ' .
'SignedHeaders=' . $signedHeaders . ', ' .
'Signature=' . $signature;
$ret['method'] = 'PUT';
$ret['url'] = 'https://' . $cfg['endpoint'] . '/' . $cfg['bucket'] . '/' . $objectKey;
$ret['syncurl'] = 'https://up.ciy.cn/up/?json=true&func=yunsync';
$ret['headers'] = array();
$ret['headers']['Authorization'] = $authorizationHeader;
$ret['headers']['x-amz-acl'] = $cfg['acl'];
$ret['headers']['x-amz-content-sha256'] = $sha256;
$ret['headers']['x-amz-date'] = $zdate;
//直传后返回url
return succjson($ret);
}
}

View File

@ -3,7 +3,7 @@
<head>
<meta charset="utf-8" />
<title>Ciyon - 众产全栈开发框架</title>
<title>Ciyon(希央) - 众产全栈开发框架</title>
<meta name="keywords" content="全栈开发框架,Golang开发框架,PHP开发框架,移动端开发框架,Vue3开发框架" />
<meta http-equiv="x-ua-compatible" content="ie=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1, minimum-scale=1, maximum-scale=1, user-scalable=0, shrink-to-fit=no" />
@ -102,6 +102,7 @@
Ciyon - 众产全栈开发框架
</div>
<div style="flex:1;text-align: right;padding:0.3em;">
<a class="btn" href="/docs/" target="_blank">开发文档</a>
<a class="btn" href="/admin/" target="_blank">在线演示</a>
</div>
</div>
@ -357,20 +358,28 @@
</div>
<div class="bgbtm"></div>
<div id="id_install" style="display:none;position: fixed;padding:1em;top:5em;left:calc(50% - 14em);width:28em;z-index: 10;background:#ffffff;border-radius: 1em;box-shadow: 0 0 30px -10px #000000;">
<div onclick="document.getElementById('id_install').style.display='none';" style="position: absolute;top:0.5em;right:0.5em;font-size:1.5em;cursor:pointer;color:#000000;line-height: 1em;">×</div>
<div onclick="close_install()" style="position: absolute;top:0.5em;right:0.5em;font-size:1.5em;cursor:pointer;color:#000000;line-height: 1em;">×</div>
<div>nginx配置成功且加载正常</div>
<div>显示日期时间则PHP运行正常</div>
<div><iframe src="install.php?act=time" style="width:100%;border:none;"></iframe></div>
<div><iframe src="install.php?act=db" style="width:100%;height:100%;border:none;"></iframe></div>
<div style="text-align:right;">
<label><input type="checkbox" name="non"/>不再显示</label>
</div>
</div>
<script src="jscss/web.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function () {
ciy_lazyimg(true);
if (document.location.host.indexOf('.local.ciy.cn') > -1) {
if (document.location.host.indexOf('.local.ciy.cn') > -1 && localStorage.getItem('__install') != 'non') {
document.getElementById('id_install').style.display = 'block';
}
});
function close_install() {
document.getElementById('id_install').style.display='none';
if(document.getElementsByName('non')[0].checked)
localStorage.setItem('__install', 'non');
}
function mbmenu_toggle(bopen) {
if (bopen)
document.getElementById('id_mb_menu').classList.add('show');

View File

@ -1000,9 +1000,10 @@ ciyfn.callfastfunc = function (dom, confirmmsg, func, postparam, succfunc, failf
ciyfn.callfunc = function (funcname, post, successfunc, opn) { //opn showload,method,fail,complete,header,timeout
opn = opn || {};
opn.header = opn.header || {};
if (!ciy_vars)
console.log('nofind ciy_vars');
opn.header[ciy_vars.tokenfield] = ciyfn.getstorage('_' + ciy_vars.tokenfield);
if (typeof (window['ciy_vars']) === 'undefined')
window.ciy_vars = {};
if (ciy_vars.tokenfield)
opn.header[ciy_vars.tokenfield] = ciyfn.getstorage('_' + ciy_vars.tokenfield);
if (document.location.search)
funcname += '&' + document.location.search.substring(1);
if (opn.showload === undefined)
@ -1045,21 +1046,27 @@ ciyfn.callfunc = function (funcname, post, successfunc, opn) { //opn showload,m
if (json.code == 1) {
if (opn._load)
opn._load.close('succ');
var re = xhr.getResponseHeader(ciy_vars.tokenfield + 're');
if (re && funcname.indexOf('restorage') == -1
&& document.location.pathname.indexOf('/rigger/cata.html') == -1
&& document.location.pathname.indexOf('/rigger/admin.html') == -1
) {
ciyfn.sendsignal(window.top, 'restorage', { show: false });
if (ciy_vars.tokenfield) {
var re = xhr.getResponseHeader(ciy_vars.tokenfield + 're');
if (re && funcname.indexOf('restorage') == -1
&& document.location.pathname.indexOf('/rigger/cata.html') == -1
&& document.location.pathname.indexOf('/rigger/admin.html') == -1
) {
ciyfn.sendsignal(window.top, 'restorage', { show: false });
}
var newauth = xhr.getResponseHeader(ciy_vars.tokenfield);
if (newauth)
ciyfn.setstorage('_' + ciy_vars.tokenfield, newauth);
}
var newauth = xhr.getResponseHeader(ciy_vars.tokenfield);
if (newauth)
ciyfn.setstorage('_' + ciy_vars.tokenfield, newauth);
if (typeof (successfunc) == 'function')
successfunc(json, xhr);
} else if (json.code == 2) {
if (top.ciy_vars.loginurl)
top.location.href = '/' + top.ciy_vars.loginurl;
if (ciy_vars.loginurl) {
if (ciy_vars.loginurl)
top.location.href = '/' + ciy_vars.loginurl;
} else {
ciyfn.alert('No Login');
}
} else if (json.code == 3) {
document.body.innerHTML = '<h1 style="text-align: center;margin-top: 0.5em;color: #ff0000;font-size: 2em;">' + json.errmsg + '</h1>';
} else {

View File

@ -907,9 +907,9 @@ ciycmpfunc.ciyselect = function (opn) {
opn.all = opn.all || opn.dom.attr('all') || '';
opn.range = opn.range || opn.dom.attr('range');
opn.minwidth = opn.minwidth || opn.dom.attr('minwidth') || '';
opn.placeholder = opn.placeholder || opn.dom.attr('placeholder') || '请选择';
opn.placeholder = ciyfn.lang(opn.placeholder || opn.dom.attr('placeholder') || '请选择');
if (typeof (opn.fn_li) != 'function')
opn.fn_li = function (range, thos) { return range.name };
opn.fn_li = function (range, thos) { return ciyfn.lang(range.name) };
var csstxt = '';
csstxt += 'ciy-select{display: block;user-select:none;}';
csstxt += 'ciy-select.mini{display: inline-block;}';
@ -928,7 +928,7 @@ ciycmpfunc.ciyselect = function (opn) {
html += '<input name="' + opn.name + '" value="" type="hidden"/>';
html += '<div class="_show"><div class="flex1 txt-over txt-left" style="padding: 0 0.5em;' + (opn.minwidth ? 'min-width:' + opn.minwidth + ';' : '') + '"></div><i></i></div>';
html += '<div class="_pullul">';
html += '<input type="text" act="search" placeholder="输入文字或拼音筛选"/>';
html += '<input type="text" act="search" placeholder="' + ciyfn.lang('输入文字或拼音筛选') + '"/>';
html += '<ul></ul>';
html += '</div>';
if (opn.hasmore && opn.name)

File diff suppressed because one or more lines are too long

View File

@ -1719,6 +1719,7 @@ textarea.tran:focus, select.tran:focus, input.tran:focus {
position: fixed;
pointer-events: auto;
-webkit-overflow-scrolling: touch;
transition: none;
}
.ciy-showend {
@ -2127,6 +2128,76 @@ textarea.tran:focus, select.tran:focus, input.tran:focus {
color: var(--txt1);
}
.md-h1 {
font-weight: bold;
font-size: 1.2em;
padding: 0.5em;
margin: 0 0.3em;
line-height: 1.5em;
text-align: left;
}
.md-h2 {
font-weight: bold;
font-size: 1.1em;
padding: 0.5em;
margin: 0 0.4em;
line-height: 1.8em;
}
.md-h3 {
font-weight: bold;
font-size: 1em;
padding: 0.5em;
margin: 0 1.5em;
line-height: 1.5em;
}
.md-h4 {
font-weight: bold;
font-size: 1em;
padding: 0.5em;
margin: 0 2em;
line-height: 1.5em;
}
.md-content {
font-size: 1.1em;
text-indent: 1em;
padding: 0.3em 1em;
line-height: 2em;
white-space: pre-wrap;
}
.md-content * {
text-indent: 0;
}
.md-code {
display: inline;
padding: 2px 4px;
margin: 0 4px;
color: #ac0e0e;
background-color: #f0f2f2;
border-radius: 4px;
}
.md-table {
border-collapse: collapse;
width: 100%;
background: var(--bg2);
}
.md-table th, .md-table td {
border: 1px solid var(--bg6);
padding: 0.5em;
text-align: left;
}
.md-table tr:hover {
background: var(--bg1);
color: var(--txt9);
}
fieldset {
border: none;
@ -2211,171 +2282,182 @@ fieldset.tips>div>ul>li {
padding: 0.2em 0.4em;
border-radius: 3px;
}
.txt-smmm {
.txt-smmm.txt-smmm.txt-smmm {
font-size: 0.7em;
}
.txt-smm {
.txt-smm.txt-smm.txt-smm {
font-size: 0.8em;
}
.txt-sm {
.txt-sm.txt-sm.txt-sm {
font-size: 0.9em;
}
.txt-lg {
.txt-lg.txt-lg.txt-lg {
font-size: 1.2em;
}
.txt-lgg {
.txt-lgg.txt-lgg.txt-lgg {
font-size: 1.3em;
}
.txt-lggg {
.txt-lggg.txt-lggg.txt-lggg {
font-size: 1.5em;
}
.txt-wb {
.txt-wb.txt-wb.txt-wb {
font-weight: bold;
}
.txt-wl {
.txt-wl.txt-wl.txt-wl {
font-weight: lighter;
}
.txt1 {
.txt1.txt1.txt1 {
color: var(--txt1);
}
.txt2 {
.txt2.txt2.txt2 {
color: var(--txt2);
}
.txt3 {
.txt3.txt3.txt3 {
color: var(--txt3);
}
.txt4 {
.txt4.txt4.txt4 {
color: var(--txt4);
}
.txt5 {
.txt5.txt5.txt5 {
color: var(--txt5);
}
.txt6 {
.txt6.txt6.txt6 {
color: var(--txt6);
}
.txt7 {
.txt7.txt7.txt7 {
color: var(--txt7);
}
.txt8 {
.txt8.txt8.txt8 {
color: var(--txt8);
}
.txt9 {
.txt9.txt9.txt9 {
color: var(--txt9);
}
.bg1 {
.bg1.bg1.bg1 {
background-color: var(--bg1);
}
.bg2 {
.bg2.bg2.bg2 {
background-color: var(--bg2);
}
.bg3 {
.bg3.bg3.bg3 {
background-color: var(--bg3);
}
.bg4 {
.bg4.bg4.bg4 {
background-color: var(--bg4);
}
.bg5 {
.bg5.bg5.bg5 {
background-color: var(--bg5);
}
.bg6 {
.bg6.bg6.bg6 {
background-color: var(--bg6);
}
.bg7 {
.bg7.bg7.bg7 {
background-color: var(--bg7);
}
.bg8 {
.bg8.bg8.bg8 {
background-color: var(--bg8);
}
.bg9 {
.bg9.bg9.bg9 {
background-color: var(--bg9);
}
.px1 {
.px0.px0.px0 {
padding-left: 0;
padding-right: 0;
}
.px1.px1.px1 {
padding-left: 0.25em;
padding-right: 0.25em;
}
.px2 {
.px2.px2.px2 {
padding-left: 0.5em;
padding-right: 0.5em;
}
.px3 {
.px3.px3.px3 {
padding-left: 0.75em;
padding-right: 0.75em;
}
.px4 {
.px4.px4.px4 {
padding-left: 1em;
padding-right: 1em;
}
.py1 {
.py0.py0.py0 {
padding-top: 0;
padding-bottom: 0;
}
.py1.py1.py1 {
padding-top: 0.25em;
padding-bottom: 0.25em;
}
.py2 {
.py2.py2.py2 {
padding-top: 0.5em;
padding-bottom: 0.5em;
}
.py3 {
.py3.py3.py3 {
padding-top: 0.75em;
padding-bottom: 0.75em;
}
.py4 {
.py4.py4.py4 {
padding-top: 1em;
padding-bottom: 1em;
}
.r1 {
.r1.r1.r1 {
border-radius: 0.25em;
}
.r2 {
.r2.r2.r2 {
border-radius: 0.5em;
}
.r3 {
.r3.r3.r3 {
border-radius: 0.75em;
}
.r4 {
.r4.r4.r4 {
border-radius: 1em;
}
.r5 {
.r5.r5.r5 {
border-radius: 2em;
}
.txt-un, .target {
.txt-un.txt-un.txt-un, .target {
text-decoration: underline;
}
@ -2383,184 +2465,184 @@ fieldset.tips>div>ul>li {
text-decoration: underline;
}
.cursor-p {
.cursor-p.cursor-p.cursor-p {
cursor: pointer;
}
.cursor-d {
.cursor-d.cursor-d.cursor-d {
cursor: default;
}
.txt-left {
.txt-left.txt-left.txt-left {
text-align: left;
}
.txt-center {
.txt-center.txt-center.txt-center {
text-align: center;
}
.txt-right {
.txt-right.txt-right.txt-right {
text-align: right;
}
.txt-just {
.txt-just.txt-just.txt-just {
text-align: justify;
}
.block {
.block.block.block {
display: block;
}
.flex {
.flex.flex.flex {
display: flex;
}
.flexcol {
.flexcol.flexcol.flexcol {
display: flex;
flex-direction: column;
}
.flex1 {
.flex1.flex1.flex1 {
flex: 1;
}
.flexnone {
.flexnone.flexnone.flexnone {
flex: none;
}
.flex-center {
.flex-center.flex-center.flex-center {
align-items: center;
}
.flex-top {
.flex-top.flex-top.flex-top {
align-items: flex-start;
}
.auto-w {
.auto-w.auto-w.auto-w {
width: auto;
}
.auto-wmin {
.auto-wmin.auto-wmin.auto-wmin {
min-width: auto;
}
.txt-nowrap {
.txt-nowrap.txt-nowrap.txt-nowrap {
white-space: nowrap;
}
.txt-over {
.txt-over.txt-over.txt-over {
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
min-width: 0;
min-width: 0;
}
.hide {
.hide.hide.hide {
display: none;
}
.noselect {
.noselect.noselect.noselect {
user-select: none;
}
.tran5 {
.tran5.tran5.tran5 {
transition: all .5s;
-webkit-transition: all .5s;
}
.tran1 {
.tran1.tran1.tran1 {
transition: all 1s;
-webkit-transition: all 1s;
}
.sta {
.sta.sta.sta {
position: static;
}
.abs {
.abs.abs.abs {
position: absolute;
}
.fix {
.fix.fix.fix {
position: fixed;
}
.rel {
.rel.rel.rel {
position: relative;
}
.sti {
.sti.sti.sti {
position: sticky;
}
.t0 {
.t0.t0.t0 {
top: 0;
}
.l0 {
.l0.l0.l0 {
left: 0;
}
.r0 {
.r0.r0.r0 {
right: 0;
}
.b0 {
.b0.b0.b0 {
bottom: 0;
}
.t1 {
.t1.t1.t1 {
top: 0.5em;
}
.l1 {
.l1.l1.l1 {
left: 0.5em;
}
.r1 {
.r1.r1.r1 {
right: 0.5em;
}
.b1 {
.b1.b1.b1 {
bottom: 0.5em;
}
.t2 {
.t2.t2.t2 {
top: 1em;
}
.l2 {
.l2.l2.l2 {
left: 1em;
}
.r2 {
.r2.r2.r2 {
right: 1em;
}
.b2 {
.b2.b2.b2 {
bottom: 1em;
}
.lf1 {
.lf1.lf1.lf1 {
line-height: 1em;
}
.lf2 {
.lf2.lf2.lf2 {
line-height: 1.5em;
}
.lf3 {
.lf3.lf3.lf3 {
line-height: 2em;
}
.lf4 {
.lf4.lf4.lf4 {
line-height: 2.5em;
}
.ti1 {
.ti1.ti1.ti1 {
text-indent: 1em;
}
.ti2 {
.ti2.ti2.ti2 {
text-indent: 2em;
}

View File

@ -8,7 +8,7 @@
* common.php 常用公共函数库
*
* 功能函数相关
* pr/var_dump PHP调试变量界面打印
* clog/var_dump PHP调试变量界面打印
* fixmonth/todate 日期月份增减函数/数字时间转字符串
* locinzone 当前经纬度是否在围栏中
* timems 获取当前微秒数

59
zciyphp/imap.php Normal file
View File

@ -0,0 +1,59 @@
<?php
/* =================================================================================
* License: GPL-2.0 license
* Author: 众产® https://ciy.cn/code
* Version: 0.1.0
*
====================================================================================*/
namespace ciy;
class imap {
public $imap_server = '';
public $user = '';
public $pass = '';
public $log = '';
private $inbox = null;
function __construct() {
}
function recvmail($lastuid = 0) {
$this->log('Connect ' . $this->imap_server);
$this->inbox = \imap_open('{' . $this->imap_server . '}INBOX', $this->user, $this->pass);
if (!$this->inbox)
return $this->log('connenct fail: ' . $this->imap_server);
$ret = array();
$ret['maxuid'] = 0;
$ret['mails'] = array();
$this->log('search unseen');
$numbers = \imap_search($this->inbox, 'UNSEEN');
if ($numbers === false) {
\imap_close($this->inbox);
return $ret;
}
$this->log('find ' . count($numbers) .'mails');
foreach ($numbers as $number) {
$this->log('read mail No. ' . $number);
$uid = \imap_uid($this->inbox, $number);
if ($uid <= $lastuid)
continue;
if ($ret['maxuid'] < $uid)
$ret['maxuid'] = $uid;
$header = \imap_headerinfo($this->inbox, $number);
if($header === false)
return $this->log('get headerinfo fail: ' . $number);
$title = \mb_decode_mimeheader($header->subject);
$content = \imap_body($this->inbox, $number);
if($content === false)
return $this->log('get body fail: ' . $number);
$ret['mails'][] = ['title' => $title, 'content' => $content, 'header' => $header];
}
\imap_close($this->inbox);
$this->log('Recv OK');
return $ret;
}
function log($message) {
$this->log .= $message . "\n";
return $message;
}
}

View File

@ -1,245 +0,0 @@
<?php
/* =================================================================================
* License: GPL-2.0 license
* Author: 众产® https://ciy.cn/code
* Version: 0.1.0
*
require PATH_ROOT . 'zcommon/mail.php';
$mailto='boi@ciy.cn';
$mailsubject="测试邮件234";
$mailbody='这里是<b>邮件</b>内容<img src="https://ciy.cn/logo.png"/>aaa<br/>bbb';
$smtpserver = "ssl://smtp.sina.com";
$smtpserverport = 465;
$smtpuser = "";
$smtppass = "";
$smtp = new smtp($smtpserver, $smtpserverport, $smtpuser, $smtppass);
//$smtp->debug = false;
$smtp->sendmail($mailto, $mailsubject, $mailbody);
====================================================================================*/
class Smtp {
public $smtp_port;
public $time_out;
public $host_name;
public $log_file;
public $relay_host;
public $debug;
public $auth;
public $user;
public $pass;
private $sock;
function __construct($relay_host = "", $smtp_port = 80, $user, $pass) {
$this->debug = FALSE;
$this->smtp_port = $smtp_port;
$this->relay_host = $relay_host;
$this->time_out = 30; //is used in fsockopen()
$this->auth = true; //auth
$this->user = $user;
$this->pass = $pass;
$this->host_name = "localhost"; //is used in HELO command
$this->log_file = "";
$this->sock = FALSE;
}
function sendmail($to, $subject, $body, $cc = '', $bcc = '', $fromUser = '', $additional_headers = '', $mailtype = 'HTML', $from = '', $replyToAddress = '') {
if(empty($from))
$from = $this->user;
if(empty($replyToAddress))
$replyToAddress = $this->user;
$mail_from = $this->get_address($this->strip_comment($from));
$body = preg_replace("/(^|(\r\n))(\.)/", "\1.\3", $body);
$header = "MIME-Version:1.0\r\n";
if ($mailtype == "HTML") {
$header .= "Content-Type:text/html; charset=utf-8\r\n";
}
$header .= "To: " . $to . "\r\n";
if ($cc != "") {
$header .= "Cc: " . $cc . "\r\n";
}
$header .= "From: $fromUser<" . $from . ">\r\n";
$header .= "Subject: =?UTF-8?B?" . base64_encode($subject) . "?=\r\n";
$header .= "Reply-To: " . $replyToAddress . "\r\n";
$header .= $additional_headers;
$header .= "Date: " . date("r") . "\r\n";
$header .= "X-Mailer:By Ciyon 1.0\r\n";
list($msec, $sec) = explode(" ", microtime());
$header .= "Message-ID: <" . date("YmdHis", $sec) . "." . ($msec * 1000000) . "." . $mail_from . ">\r\n";
//若需要开启邮件跟踪服务,请使用以下代码设置跟踪链接头。前置条件和约束见文档"如何开启数据跟踪功能?"
//$header .= "Content-Transfer-Encoding: quoted-printable\r\n";
//$header .= "X-AliDM-Trace: ". base64_encode(json_encode(['TagName'=>'用户创建的Tag','OpenTrace'=>"1"]))."\r\n";
$TO = explode(",", $this->strip_comment($to));
if ($cc != "") {
$TO = array_merge($TO, explode(",", $this->strip_comment($cc)));
}
if ($bcc != "") {
$TO = array_merge($TO, explode(",", $this->strip_comment($bcc)));
}
$sent = '';
foreach ($TO as $rcpt_to) {
$rcpt_to = $this->get_address($rcpt_to);
if (!$this->smtp_sockopen($rcpt_to)) {
$this->log_write("Error: Cannot send email to " . $rcpt_to . "\n");
$sent = '邮件发送到'.$rcpt_to.'失败,连接错误';
continue;
}
if ($this->smtp_send($this->host_name, $mail_from, $rcpt_to, $header, $body)) {
$this->log_write("E-mail has been sent to <" . $rcpt_to . ">\n");
} else {
$this->log_write("Error: Cannot send email to <" . $rcpt_to . ">\n");
$sent = '邮件发送到'.$rcpt_to.'失败,发送报错';
}
fclose($this->sock);
$this->log_write("Disconnected from remote host\n");
}
return $sent;
}
function smtp_send($helo, $from, $to, $header, $body = "") {
if (!$this->smtp_putcmd("HELO", $helo)) {
return $this->smtp_error("sending HELO command");
}
if ($this->auth) {
if (!$this->smtp_putcmd("AUTH LOGIN", base64_encode($this->user))) {
return $this->smtp_error("sending HELO command");
}
if (!$this->smtp_putcmd("", base64_encode($this->pass))) {
return $this->smtp_error("sending HELO command");
}
}
if (!$this->smtp_putcmd("MAIL", "FROM:<" . $from . ">")) {
return $this->smtp_error("sending MAIL FROM command");
}
if (!$this->smtp_putcmd("RCPT", "TO:<" . $to . ">")) {
return $this->smtp_error("sending RCPT TO command");
}
if (!$this->smtp_putcmd("DATA")) {
return $this->smtp_error("sending DATA command");
}
if (!$this->smtp_message($header, $body)) {
return $this->smtp_error("sending message");
}
if (!$this->smtp_eom()) {
return $this->smtp_error("sending <CR><LF>.<CR><LF> [EOM]");
}
if (!$this->smtp_putcmd("QUIT")) {
return $this->smtp_error("sending QUIT command");
}
return TRUE;
}
function smtp_sockopen($address) {
if ($this->relay_host == "") {
return $this->smtp_sockopen_mx($address);
} else {
return $this->smtp_sockopen_relay();
}
}
function smtp_sockopen_relay() {
$this->log_write("Trying to " . $this->relay_host . ":" . $this->smtp_port . "\n");
$this->sock = @fsockopen($this->relay_host, $this->smtp_port, $errno, $errstr, $this->time_out);
if (!($this->sock && $this->smtp_ok())) {
$this->log_write("Error: Cannot connenct to relay host " . $this->relay_host . "\n");
$this->log_write("Error: " . $errstr . " (" . $errno . ")\n");
return FALSE;
}
$this->log_write("Connected to relay host " . $this->relay_host . "\n");
return TRUE;
}
function smtp_sockopen_mx($address) {
$domain = preg_replace("/^.+@([^@]+)$/", "\1", $address);
if (!@getmxrr($domain, $MXHOSTS)) {
$this->log_write("Error: Cannot resolve MX \"" . $domain . "\"\n");
return FALSE;
}
foreach ($MXHOSTS as $host) {
$this->log_write("Trying to " . $host . ":" . $this->smtp_port . "\n");
$this->sock = @fsockopen($host, $this->smtp_port, $errno, $errstr, $this->time_out);
if (!($this->sock && $this->smtp_ok())) {
$this->log_write("Warning: Cannot connect to mx host " . $host . "\n");
$this->log_write("Error: " . $errstr . " (" . $errno . ")\n");
continue;
}
$this->log_write("Connected to mx host " . $host . "\n");
return TRUE;
}
$this->log_write("Error: Cannot connect to any mx hosts (" . implode(", ", $MXHOSTS) . ")\n");
return FALSE;
}
function smtp_message($header, $body) {
fputs($this->sock, $header . "\r\n" . $body);
$this->smtp_debug("> " . str_replace("\r\n", "\n" . "> ", $header . "\n> " . $body . "\n> "));
return TRUE;
}
function smtp_eom() {
fputs($this->sock, "\r\n.\r\n");
$this->smtp_debug(". [EOM]\n");
return $this->smtp_ok();
}
function smtp_ok() {
$response = str_replace("\r\n", "", fgets($this->sock, 512));
$this->smtp_debug($response . "\n");
if (!preg_match("/^[23]/", $response)) {
fputs($this->sock, "QUIT\r\n");
fgets($this->sock, 512);
$this->log_write("Error: Remote host returned \"" . $response . "\"\n");
return FALSE;
}
return TRUE;
}
function smtp_putcmd($cmd, $arg = "") {
if ($arg != "") {
if ($cmd == "") $cmd = $arg;
else $cmd = $cmd . " " . $arg;
}
fputs($this->sock, $cmd . "\r\n");
$this->smtp_debug("> " . $cmd . "\n");
return $this->smtp_ok();
}
function smtp_error($string) {
$this->log_write("Error: Error occurred while " . $string . ".\n");
return FALSE;
}
function log_write($message) {
$this->smtp_debug($message);
if ($this->log_file == "") {
return TRUE;
}
$message = date("M d H:i:s ") . get_current_user() . "[" . getmypid() . "]: " . $message;
if (!@file_exists($this->log_file) || !($fp = @fopen($this->log_file, "a"))) {
$this->smtp_debug("Warning: Cannot open log file \"" . $this->log_file . "\"\n");
return FALSE;
}
flock($fp, LOCK_EX);
fputs($fp, $message);
fclose($fp);
return TRUE;
}
function strip_comment($address) {
$comment = "/\([^()]*\)/";
while (preg_match($comment, $address)) {
$address = preg_replace($comment, "", $address);
}
return $address;
}
function get_address($address) {
$address = preg_replace("/([ \t\r\n])+/", "", $address);
$address = preg_replace("/^.*<(.+)>.*$/", "\1", $address);
return $address;
}
function smtp_debug($message) {
if ($this->debug) {
echo $message;
}
}
}

116
zciyphp/smtp.php Normal file
View File

@ -0,0 +1,116 @@
<?php
/* =================================================================================
* License: GPL-2.0 license
* Author: 众产® https://ciy.cn/code
* Version: 0.1.1
*
$mail = new \ciy\smtp();
$mail->smtp_server = 'ssl://smtp.xxx.com';
$mail->smtp_port = 465;
$mail->user = 'xxx@ciy.cn';
$mail->pass = '123456';
$param = array();
$param['mailto'] = '345345345@qq.com';
$param['title'] = '邮件标题xxx';
$param['content'] = '支持html的邮件内容';
$retmail = $mail->sendmail($param);
//clog($mail->log);
if(is_string($retmail))
return errjson($retmail);
====================================================================================*/
namespace ciy;
class smtp {
public $smtp_server = '';
public $smtp_port = 465;
public $timeout = 30;
public $user = '';
public $pass = '';
public $log = '';
private $sock = false;
function __construct() {
}
function sendmail($param) {
$mailto = $param['mailto']; // a@b.c,d@b.c
$title = $param['title'];
$content = $param['content'];
$cc = isset($param['cc']) ? $param['cc'] : ''; // a@b.c,d@b.c
$bcc = isset($param['bcc']) ? $param['bcc'] : ''; // a@b.c,d@b.c
$content = preg_replace("/(^|(\r\n))(\.)/", "\1.\3", $content);
$header = "MIME-Version:1.0\r\n";
$header .= "Content-Type:text/html; charset=utf-8\r\n";
$header .= 'To: ' . $mailto . "\r\n";
if ($cc != '')
$header .= 'Cc: ' . $cc . "\r\n";
$header .= 'From: <' . $this->user . ">\r\n";
$header .= "Subject: =?UTF-8?B?" . base64_encode($title) . "?=\r\n";
$header .= 'Reply-To: ' . $this->user . "\r\n";
//$header .= $additional_headers;
$header .= 'Date: ' . date('r') . "\r\n";
$header .= "X-Mailer:By Ciyon 1.0\r\n";
list($msec, $sec) = explode(' ', microtime());
$header .= 'Message-ID: <' . date('YmdHis', $sec) . '.' . ($msec * 1000000) . '.' . $this->user . ">\r\n";
//$header .= "Content-Transfer-Encoding: quoted-printable\r\n";
//$header .= "X-AliDM-Trace: ". base64_encode(json_encode(['TagName'=>'用户创建的Tag','OpenTrace'=>"1"]))."\r\n";
$TO = explode(',', $mailto);
if ($cc != '')
$TO = array_merge($TO, explode(',', $cc));
if ($bcc != '')
$TO = array_merge($TO, explode(',', $bcc));
foreach ($TO as $rcpt_to) {
$this->log('Connect ' . $this->smtp_server . ':' . $this->smtp_port);
$this->sock = @fsockopen($this->smtp_server, $this->smtp_port, $errno, $errstr, $this->timeout);
if (!($this->sock && $this->checksuccess()))
return $this->log('connenct fail: ' . $errstr . ' (' . $errno . ')');
if (!$this->sendcmd('HELO', 'localhost'))
return $this->log('sending HELO fail');
if (!$this->sendcmd('AUTH LOGIN', base64_encode($this->user)))
return $this->log('mail user fail');
if (!$this->sendcmd(base64_encode($this->pass)))
return $this->log('password fail');
if (!$this->sendcmd('MAIL', 'FROM:<' . $this->user . '>'))
return $this->log('sending MAIL fail');
if (!$this->sendcmd('RCPT', 'TO:<' . $rcpt_to . '>'))
return $this->log('sending RCPT fail');
if (!$this->sendcmd('DATA'))
return $this->log('sending DATA fail');
fputs($this->sock, $header . "\r\n" . $content);
$this->log('> ' . str_replace("\r\n", "\n" . '> ', $header . "\n> " . $content . "\n> "));
fputs($this->sock, "\r\n.\r\n");
$this->log('. [EOM]');
if (!$this->checksuccess())
return $this->log('sending [EOM] fail');
$this->sendcmd('QUIT');
fclose($this->sock);
$this->log('Send OK');
}
return true;
}
function checksuccess() {
$response = str_replace("\r\n", '', fgets($this->sock, 512));
$this->log('< ' . $response);
if (!preg_match("/^[23]/", $response)) {
fputs($this->sock, "QUIT\r\n");
fgets($this->sock, 512);
$this->log('Remote host returned: ' . $response);
return false;
}
return true;
}
function sendcmd($cmd, $arg = '') {
if (!empty($arg))
$cmd .= ' ' . $arg;
fputs($this->sock, $cmd . "\r\n");
$this->log('> ' . $cmd);
return $this->checksuccess();
}
function log($message) {
$this->log .= $message . "\n";
return $message;
}
}