diff --git a/bot/.env.example b/bot/.env.example new file mode 100755 index 0000000..fd234f9 --- /dev/null +++ b/bot/.env.example @@ -0,0 +1,6 @@ +APP_NAME=入魔入佛 +TIMEZONE=Asia/Shanghai +DB_HOST=127.0.0.1 +DB_DATABASE=xxx +DB_USERNAME=root +DB_PASSWORD=xxxxxx diff --git a/bot/.gitignore b/bot/.gitignore new file mode 100644 index 0000000..147d0c1 --- /dev/null +++ b/bot/.gitignore @@ -0,0 +1,5 @@ +.idea +.DS_Store +logs/workerman.log +logs/workerman.pid +.env diff --git a/bot/LICENSE b/bot/LICENSE new file mode 100644 index 0000000..2071b23 --- /dev/null +++ b/bot/LICENSE @@ -0,0 +1,9 @@ +MIT License + +Copyright (c) + +Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/bot/bot/bot_example.php b/bot/bot/bot_example.php new file mode 100644 index 0000000..26a899d --- /dev/null +++ b/bot/bot/bot_example.php @@ -0,0 +1,23 @@ +count = 1; +$bot_loop->name = Tool::ini('APP_NAME'); + +function ExampleFunc() +{ + $db = Db::get(); + $config = $db->getRow('select * from configs where id = ?', [1]); + var_dump($config); +} + +$bot_loop->onWorkerStart = function () { + ExampleFunc(); + Timer::add(3, function () { + ExampleFunc(); + }); +}; diff --git a/bot/logs/给这个文件夹权限 b/bot/logs/给这个文件夹权限 new file mode 100644 index 0000000..e69de29 diff --git a/bot/start.php b/bot/start.php new file mode 100755 index 0000000..4011ce3 --- /dev/null +++ b/bot/start.php @@ -0,0 +1,21 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +/** + * Autoload. + */ +class Autoloader +{ + /** + * Autoload root path. + * + * @var string + */ + protected static $_autoloadRootPath = ''; + + /** + * Set autoload root path. + * + * @param string $root_path + * @return void + */ + public static function setRootPath($root_path) + { + self::$_autoloadRootPath = $root_path; + } + + /** + * Load files by namespace. + * + * @param string $name + * @return boolean + */ + public static function loadByNamespace($name) + { + $class_path = str_replace('\\', DIRECTORY_SEPARATOR, $name); + if (strpos($name, 'Workerman\\') === 0) { + $class_file = __DIR__ . substr($class_path, strlen('Workerman')) . '.php'; + } else { + if (self::$_autoloadRootPath) { + $class_file = self::$_autoloadRootPath . DIRECTORY_SEPARATOR . $class_path . '.php'; + } + if (empty($class_file) || !is_file($class_file)) { + $class_file = __DIR__ . DIRECTORY_SEPARATOR . '..' . DIRECTORY_SEPARATOR . "$class_path.php"; + } + } + + if (is_file($class_file)) { + require_once($class_file); + if (class_exists($name, false)) { + return true; + } + } + return false; + } +} + +spl_autoload_register('\Workerman\Autoloader::loadByNamespace'); \ No newline at end of file diff --git a/bot/workerman/Connection/AsyncTcpConnection.php b/bot/workerman/Connection/AsyncTcpConnection.php new file mode 100755 index 0000000..e785bf7 --- /dev/null +++ b/bot/workerman/Connection/AsyncTcpConnection.php @@ -0,0 +1,372 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Lib\Timer; +use Workerman\Worker; +use Exception; + +/** + * AsyncTcpConnection. + */ +class AsyncTcpConnection extends TcpConnection +{ + /** + * Emitted when socket connection is successfully established. + * + * @var callback + */ + public $onConnect = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Status. + * + * @var int + */ + protected $_status = self::STATUS_INITIAL; + + /** + * Remote host. + * + * @var string + */ + protected $_remoteHost = ''; + + /** + * Remote port. + * + * @var int + */ + protected $_remotePort = 80; + + /** + * Connect start time. + * + * @var string + */ + protected $_connectStartTime = 0; + + /** + * Remote URI. + * + * @var string + */ + protected $_remoteURI = ''; + + /** + * Context option. + * + * @var array + */ + protected $_contextOption = null; + + /** + * Reconnect timer. + * + * @var int + */ + protected $_reconnectTimer = null; + + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'ssl', + 'sslv2' => 'sslv2', + 'sslv3' => 'sslv3', + 'tls' => 'tls' + ); + + /** + * Construct. + * + * @param string $remote_address + * @param array $context_option + * @throws Exception + */ + public function __construct($remote_address, $context_option = null) + { + $address_info = parse_url($remote_address); + if (!$address_info) { + list($scheme, $this->_remoteAddress) = explode(':', $remote_address, 2); + if (!$this->_remoteAddress) { + Worker::safeEcho(new \Exception('bad remote_address')); + } + } else { + if (!isset($address_info['port'])) { + $address_info['port'] = 80; + } + if (!isset($address_info['path'])) { + $address_info['path'] = '/'; + } + if (!isset($address_info['query'])) { + $address_info['query'] = ''; + } else { + $address_info['query'] = '?' . $address_info['query']; + } + $this->_remoteAddress = "{$address_info['host']}:{$address_info['port']}"; + $this->_remoteHost = $address_info['host']; + $this->_remotePort = $address_info['port']; + $this->_remoteURI = "{$address_info['path']}{$address_info['query']}"; + $scheme = isset($address_info['scheme']) ? $address_info['scheme'] : 'tcp'; + } + + $this->id = $this->_id = self::$_idRecorder++; + if(PHP_INT_MAX === self::$_idRecorder){ + self::$_idRecorder = 0; + } + // Check application layer protocol class. + if (!isset(self::$_builtinTransports[$scheme])) { + $scheme = ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } else { + $this->transport = self::$_builtinTransports[$scheme]; + } + + // For statistics. + self::$statistics['connection_count']++; + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->_contextOption = $context_option; + static::$connections[$this->_id] = $this; + } + + /** + * Do connect. + * + * @return void + */ + public function connect() + { + if ($this->_status !== self::STATUS_INITIAL && $this->_status !== self::STATUS_CLOSING && + $this->_status !== self::STATUS_CLOSED) { + return; + } + $this->_status = self::STATUS_CONNECTING; + $this->_connectStartTime = microtime(true); + if ($this->transport !== 'unix') { + // Open socket connection asynchronously. + if ($this->_contextOption) { + $context = stream_context_create($this->_contextOption); + $this->_socket = stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", + $errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT, $context); + } else { + $this->_socket = stream_socket_client("tcp://{$this->_remoteHost}:{$this->_remotePort}", + $errno, $errstr, 0, STREAM_CLIENT_ASYNC_CONNECT); + } + } else { + $this->_socket = stream_socket_client("{$this->transport}://{$this->_remoteAddress}", $errno, $errstr, 0, + STREAM_CLIENT_ASYNC_CONNECT); + } + // If failed attempt to emit onError callback. + if (!$this->_socket || !is_resource($this->_socket)) { + $this->emitError(WORKERMAN_CONNECT_FAIL, $errstr); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + return; + } + // Add socket to global event loop waiting connection is successfully established or faild. + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'checkConnection')); + // For windows. + if(DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_EXCEPT, array($this, 'checkConnection')); + } + } + + /** + * Reconnect. + * + * @param int $after + * @return void + */ + public function reconnect($after = 0) + { + $this->_status = self::STATUS_INITIAL; + static::$connections[$this->_id] = $this; + if ($this->_reconnectTimer) { + Timer::del($this->_reconnectTimer); + } + if ($after > 0) { + $this->_reconnectTimer = Timer::add($after, array($this, 'connect'), null, false); + return; + } + $this->connect(); + } + + /** + * CancelReconnect. + */ + public function cancelReconnect() + { + if ($this->_reconnectTimer) { + Timer::del($this->_reconnectTimer); + } + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteHost() + { + return $this->_remoteHost; + } + + /** + * Get remote URI. + * + * @return string + */ + public function getRemoteURI() + { + return $this->_remoteURI; + } + + /** + * Try to emit onError callback. + * + * @param int $code + * @param string $msg + * @return void + */ + protected function emitError($code, $msg) + { + $this->_status = self::STATUS_CLOSING; + if ($this->onError) { + try { + call_user_func($this->onError, $this, $code, $msg); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + + /** + * Check connection is successfully established or faild. + * + * @param resource $socket + * @return void + */ + public function checkConnection() + { + // Remove EV_EXPECT for windows. + if(DIRECTORY_SEPARATOR === '\\') { + Worker::$globalEvent->del($this->_socket, EventInterface::EV_EXCEPT); + } + + // Remove write listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + + if ($this->_status != self::STATUS_CONNECTING) { + return; + } + + // Check socket state. + if ($address = stream_socket_get_name($this->_socket, true)) { + // Nonblocking. + stream_set_blocking($this->_socket, 0); + // Compatible with hhvm + if (function_exists('stream_set_read_buffer')) { + stream_set_read_buffer($this->_socket, 0); + } + // Try to open keepalive for tcp and disable Nagle algorithm. + if (function_exists('socket_import_stream') && $this->transport === 'tcp') { + $raw_socket = socket_import_stream($this->_socket); + socket_set_option($raw_socket, SOL_SOCKET, SO_KEEPALIVE, 1); + socket_set_option($raw_socket, SOL_TCP, TCP_NODELAY, 1); + } + + // SSL handshake. + if ($this->transport === 'ssl') { + $this->_sslHandshakeCompleted = $this->doSslHandshake($this->_socket); + if ($this->_sslHandshakeCompleted === false) { + return; + } + } else { + // There are some data waiting to send. + if ($this->_sendBuffer) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + } + + // Register a listener waiting read event. + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + + $this->_status = self::STATUS_ESTABLISHED; + $this->_remoteAddress = $address; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + call_user_func($this->onConnect, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Try to emit protocol::onConnect + if (method_exists($this->protocol, 'onConnect')) { + try { + call_user_func(array($this->protocol, 'onConnect'), $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } else { + // Connection failed. + $this->emitError(WORKERMAN_CONNECT_FAIL, 'connect ' . $this->_remoteAddress . ' fail after ' . round(microtime(true) - $this->_connectStartTime, 4) . ' seconds'); + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + if ($this->_status === self::STATUS_CLOSED) { + $this->onConnect = null; + } + } + } +} diff --git a/bot/workerman/Connection/AsyncUdpConnection.php b/bot/workerman/Connection/AsyncUdpConnection.php new file mode 100755 index 0000000..fdae348 --- /dev/null +++ b/bot/workerman/Connection/AsyncUdpConnection.php @@ -0,0 +1,209 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use Exception; + +/** + * AsyncTcpConnection. + */ +class AsyncUdpConnection extends UdpConnection +{ + /** + * Emitted when socket connection is successfully established. + * + * @var callback + */ + public $onConnect = null; + + /** + * Emitted when socket connection closed. + * + * @var callback + */ + public $onClose = null; + + /** + * Connected or not. + * + * @var bool + */ + protected $connected = false; + + /** + * Context option. + * + * @var array + */ + protected $_contextOption = null; + + /** + * Construct. + * + * @param string $remote_address + * @throws Exception + */ + public function __construct($remote_address, $context_option = null) + { + // Get the application layer communication protocol and listening address. + list($scheme, $address) = explode(':', $remote_address, 2); + // Check application layer protocol class. + if ($scheme !== 'udp') { + $scheme = ucfirst($scheme); + $this->protocol = '\\Protocols\\' . $scheme; + if (!class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + } + + $this->_remoteAddress = substr($address, 2); + $this->_contextOption = $context_option; + } + + /** + * For udp package. + * + * @param resource $socket + * @return bool + */ + public function baseRead($socket) + { + $recv_buffer = stream_socket_recvfrom($socket, Worker::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + + if ($this->onMessage) { + if ($this->protocol) { + $parser = $this->protocol; + $recv_buffer = $parser::decode($recv_buffer, $this); + } + ConnectionInterface::$statistics['total_request']++; + try { + call_user_func($this->onMessage, $this, $recv_buffer); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return true; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|boolean + */ + public function send($send_buffer, $raw = false) + { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return null; + } + } + if ($this->connected === false) { + $this->connect(); + } + return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0); + } + + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + fclose($this->_socket); + $this->connected = false; + // Try to emit onClose callback. + if ($this->onClose) { + try { + call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $this->onConnect = $this->onMessage = $this->onClose = null; + return true; + } + + /** + * Connect. + * + * @return void + */ + public function connect() + { + if ($this->connected === true) { + return; + } + if ($this->_contextOption) { + $context = stream_context_create($this->_contextOption); + $this->_socket = stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg, + 30, STREAM_CLIENT_CONNECT, $context); + } else { + $this->_socket = stream_socket_client("udp://{$this->_remoteAddress}", $errno, $errmsg); + } + + if (!$this->_socket) { + Worker::safeEcho(new \Exception($errmsg)); + return; + } + + stream_set_blocking($this->_socket, false); + + if ($this->onMessage) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + } + $this->connected = true; + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + call_user_func($this->onConnect, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + +} diff --git a/bot/workerman/Connection/ConnectionInterface.php b/bot/workerman/Connection/ConnectionInterface.php new file mode 100755 index 0000000..622862a --- /dev/null +++ b/bot/workerman/Connection/ConnectionInterface.php @@ -0,0 +1,125 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * ConnectionInterface. + */ +abstract class ConnectionInterface +{ + /** + * Statistics for status command. + * + * @var array + */ + public static $statistics = array( + 'connection_count' => 0, + 'total_request' => 0, + 'throw_exception' => 0, + 'send_fail' => 0, + ); + + /** + * Emitted when data is received. + * + * @var callback + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callback + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callback + */ + public $onError = null; + + /** + * Sends data on the connection. + * + * @param mixed $send_buffer + * @return void|boolean + */ + abstract public function send($send_buffer); + + /** + * Get remote IP. + * + * @return string + */ + abstract public function getRemoteIp(); + + /** + * Get remote port. + * + * @return int + */ + abstract public function getRemotePort(); + + /** + * Get remote address. + * + * @return string + */ + abstract public function getRemoteAddress(); + + /** + * Get local IP. + * + * @return string + */ + abstract public function getLocalIp(); + + /** + * Get local port. + * + * @return int + */ + abstract public function getLocalPort(); + + /** + * Get local address. + * + * @return string + */ + abstract public function getLocalAddress(); + + /** + * Is ipv4. + * + * @return bool + */ + abstract public function isIPv4(); + + /** + * Is ipv6. + * + * @return bool + */ + abstract public function isIPv6(); + + /** + * Close connection. + * + * @param $data + * @return void + */ + abstract public function close($data = null); +} diff --git a/bot/workerman/Connection/TcpConnection.php b/bot/workerman/Connection/TcpConnection.php new file mode 100755 index 0000000..98419d3 --- /dev/null +++ b/bot/workerman/Connection/TcpConnection.php @@ -0,0 +1,1005 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use Exception; + +/** + * TcpConnection. + */ +class TcpConnection extends ConnectionInterface +{ + /** + * Read buffer size. + * + * @var int + */ + const READ_BUFFER_SIZE = 65535; + + /** + * Status initial. + * + * @var int + */ + const STATUS_INITIAL = 0; + + /** + * Status connecting. + * + * @var int + */ + const STATUS_CONNECTING = 1; + + /** + * Status connection established. + * + * @var int + */ + const STATUS_ESTABLISHED = 2; + + /** + * Status closing. + * + * @var int + */ + const STATUS_CLOSING = 4; + + /** + * Status closed. + * + * @var int + */ + const STATUS_CLOSED = 8; + + /** + * Emitted when data is received. + * + * @var callback + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callback + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callback + */ + public $onError = null; + + /** + * Emitted when the send buffer becomes full. + * + * @var callback + */ + public $onBufferFull = null; + + /** + * Emitted when the send buffer becomes empty. + * + * @var callback + */ + public $onBufferDrain = null; + + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Transport (tcp/udp/unix/ssl). + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Which worker belong to. + * + * @var Worker + */ + public $worker = null; + + /** + * Bytes read. + * + * @var int + */ + public $bytesRead = 0; + + /** + * Bytes written. + * + * @var int + */ + public $bytesWritten = 0; + + /** + * Connection->id. + * + * @var int + */ + public $id = 0; + + /** + * A copy of $worker->id which used to clean up the connection in worker->connections + * + * @var int + */ + protected $_id = 0; + + /** + * Sets the maximum send buffer size for the current connection. + * OnBufferFull callback will be emited When the send buffer is full. + * + * @var int + */ + public $maxSendBufferSize = 1048576; + + /** + * Default send buffer size. + * + * @var int + */ + public static $defaultMaxSendBufferSize = 1048576; + + /** + * Sets the maximum acceptable packet size for the current connection. + * + * @var int + */ + public $maxPackageSize = 1048576; + + /** + * Default maximum acceptable packet size. + * + * @var int + */ + public static $defaultMaxPackageSize = 10485760; + + /** + * Id recorder. + * + * @var int + */ + protected static $_idRecorder = 1; + + /** + * Socket + * + * @var resource + */ + protected $_socket = null; + + /** + * Send buffer. + * + * @var string + */ + protected $_sendBuffer = ''; + + /** + * Receive buffer. + * + * @var string + */ + protected $_recvBuffer = ''; + + /** + * Current package length. + * + * @var int + */ + protected $_currentPackageLength = 0; + + /** + * Connection status. + * + * @var int + */ + protected $_status = self::STATUS_ESTABLISHED; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Is paused. + * + * @var bool + */ + protected $_isPaused = false; + + /** + * SSL handshake completed or not. + * + * @var bool + */ + protected $_sslHandshakeCompleted = false; + + /** + * All connection instances. + * + * @var array + */ + public static $connections = array(); + + /** + * Status to string. + * + * @var array + */ + public static $_statusToString = array( + self::STATUS_INITIAL => 'INITIAL', + self::STATUS_CONNECTING => 'CONNECTING', + self::STATUS_ESTABLISHED => 'ESTABLISHED', + self::STATUS_CLOSING => 'CLOSING', + self::STATUS_CLOSED => 'CLOSED', + ); + + + /** + * Adding support of custom functions within protocols + * + * @param string $name + * @param array $arguments + * @return void + */ + public function __call($name, $arguments) { + // Try to emit custom function within protocol + if (method_exists($this->protocol, $name)) { + try { + return call_user_func(array($this->protocol, $name), $this, $arguments); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address = '') + { + self::$statistics['connection_count']++; + $this->id = $this->_id = self::$_idRecorder++; + if(self::$_idRecorder === PHP_INT_MAX){ + self::$_idRecorder = 0; + } + $this->_socket = $socket; + stream_set_blocking($this->_socket, 0); + // Compatible with hhvm + if (function_exists('stream_set_read_buffer')) { + stream_set_read_buffer($this->_socket, 0); + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->maxSendBufferSize = self::$defaultMaxSendBufferSize; + $this->maxPackageSize = self::$defaultMaxPackageSize; + $this->_remoteAddress = $remote_address; + static::$connections[$this->id] = $this; + } + + /** + * Get status. + * + * @param bool $raw_output + * + * @return int + */ + public function getStatus($raw_output = true) + { + if ($raw_output) { + return $this->_status; + } + return self::$_statusToString[$this->_status]; + } + + /** + * Sends data on the connection. + * + * @param mixed $send_buffer + * @param bool $raw + * @return bool|null + */ + public function send($send_buffer, $raw = false) + { + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return false; + } + + // Try to call protocol::encode($send_buffer) before sending. + if (false === $raw && $this->protocol !== null) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return null; + } + } + + if ($this->_status !== self::STATUS_ESTABLISHED || + ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) + ) { + if ($this->_sendBuffer) { + if ($this->bufferIsFull()) { + self::$statistics['send_fail']++; + return false; + } + } + $this->_sendBuffer .= $send_buffer; + $this->checkBufferWillFull(); + return null; + } + + // Attempt to send data directly. + if ($this->_sendBuffer === '') { + if ($this->transport === 'ssl') { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + $this->_sendBuffer = $send_buffer; + $this->checkBufferWillFull(); + return null; + } + set_error_handler(function(){}); + $len = fwrite($this->_socket, $send_buffer); + restore_error_handler(); + // send successful. + if ($len === strlen($send_buffer)) { + $this->bytesWritten += $len; + return true; + } + // Send only part of the data. + if ($len > 0) { + $this->_sendBuffer = substr($send_buffer, $len); + $this->bytesWritten += $len; + } else { + // Connection closed? + if (!is_resource($this->_socket) || feof($this->_socket)) { + self::$statistics['send_fail']++; + if ($this->onError) { + try { + call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'client closed'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $this->destroy(); + return false; + } + $this->_sendBuffer = $send_buffer; + } + Worker::$globalEvent->add($this->_socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + // Check if the send buffer will be full. + $this->checkBufferWillFull(); + return null; + } else { + if ($this->bufferIsFull()) { + self::$statistics['send_fail']++; + return false; + } + + $this->_sendBuffer .= $send_buffer; + // Check if the send buffer is full. + $this->checkBufferWillFull(); + } + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = strrpos($this->_remoteAddress, ':'); + if ($pos) { + return substr($this->_remoteAddress, 0, $pos); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int)substr(strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = strrpos($address, ':'); + if (!$pos) { + return ''; + } + return substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)substr(strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + return (string)@stream_socket_get_name($this->_socket, false); + } + + /** + * Get send buffer queue size. + * + * @return integer + */ + public function getSendBufferQueueSize() + { + return strlen($this->_sendBuffer); + } + + /** + * Get recv buffer queue size. + * + * @return integer + */ + public function getRecvBufferQueueSize() + { + return strlen($this->_recvBuffer); + } + + /** + * Is ipv4. + * + * return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Pauses the reading of data. That is onMessage will not be emitted. Useful to throttle back an upload. + * + * @return void + */ + public function pauseRecv() + { + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + $this->_isPaused = true; + } + + /** + * Resumes reading after a call to pauseRecv. + * + * @return void + */ + public function resumeRecv() + { + if ($this->_isPaused === true) { + Worker::$globalEvent->add($this->_socket, EventInterface::EV_READ, array($this, 'baseRead')); + $this->_isPaused = false; + $this->baseRead($this->_socket, false); + } + } + + + + /** + * Base read handler. + * + * @param resource $socket + * @param bool $check_eof + * @return void + */ + public function baseRead($socket, $check_eof = true) + { + // SSL handshake. + if ($this->transport === 'ssl' && $this->_sslHandshakeCompleted !== true) { + if ($this->doSslHandshake($socket)) { + $this->_sslHandshakeCompleted = true; + if ($this->_sendBuffer) { + Worker::$globalEvent->add($socket, EventInterface::EV_WRITE, array($this, 'baseWrite')); + } + } else { + return; + } + } + + set_error_handler(function(){}); + $buffer = fread($socket, self::READ_BUFFER_SIZE); + restore_error_handler(); + + // Check connection closed. + if ($buffer === '' || $buffer === false) { + if ($check_eof && (feof($socket) || !is_resource($socket) || $buffer === false)) { + $this->destroy(); + return; + } + } else { + $this->bytesRead += strlen($buffer); + $this->_recvBuffer .= $buffer; + } + + // If the application layer protocol has been set up. + if ($this->protocol !== null) { + $parser = $this->protocol; + while ($this->_recvBuffer !== '' && !$this->_isPaused) { + // The current packet length is known. + if ($this->_currentPackageLength) { + // Data is not enough for a package. + if ($this->_currentPackageLength > strlen($this->_recvBuffer)) { + break; + } + } else { + // Get current package length. + set_error_handler(function($code, $msg, $file, $line){ + Worker::safeEcho("$msg in file $file on line $line\n"); + }); + $this->_currentPackageLength = $parser::input($this->_recvBuffer, $this); + restore_error_handler(); + // The packet length is unknown. + if ($this->_currentPackageLength === 0) { + break; + } elseif ($this->_currentPackageLength > 0 && $this->_currentPackageLength <= $this->maxPackageSize) { + // Data is not enough for a package. + if ($this->_currentPackageLength > strlen($this->_recvBuffer)) { + break; + } + } // Wrong package. + else { + Worker::safeEcho('error package. package_length=' . var_export($this->_currentPackageLength, true)); + $this->destroy(); + return; + } + } + + // The data is enough for a packet. + self::$statistics['total_request']++; + // The current packet length is equal to the length of the buffer. + if (strlen($this->_recvBuffer) === $this->_currentPackageLength) { + $one_request_buffer = $this->_recvBuffer; + $this->_recvBuffer = ''; + } else { + // Get a full package from the buffer. + $one_request_buffer = substr($this->_recvBuffer, 0, $this->_currentPackageLength); + // Remove the current package from the receive buffer. + $this->_recvBuffer = substr($this->_recvBuffer, $this->_currentPackageLength); + } + // Reset the current packet length to 0. + $this->_currentPackageLength = 0; + if (!$this->onMessage) { + continue; + } + try { + // Decode request buffer before Emitting onMessage callback. + call_user_func($this->onMessage, $this, $parser::decode($one_request_buffer, $this)); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return; + } + + if ($this->_recvBuffer === '' || $this->_isPaused) { + return; + } + + // Applications protocol is not set. + self::$statistics['total_request']++; + if (!$this->onMessage) { + $this->_recvBuffer = ''; + return; + } + try { + call_user_func($this->onMessage, $this, $this->_recvBuffer); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + // Clean receive buffer. + $this->_recvBuffer = ''; + } + + /** + * Base write handler. + * + * @return void|bool + */ + public function baseWrite() + { + set_error_handler(function(){}); + if ($this->transport === 'ssl') { + $len = fwrite($this->_socket, $this->_sendBuffer, 8192); + } else { + $len = fwrite($this->_socket, $this->_sendBuffer); + } + restore_error_handler(); + if ($len === strlen($this->_sendBuffer)) { + $this->bytesWritten += $len; + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + $this->_sendBuffer = ''; + // Try to emit onBufferDrain callback when the send buffer becomes empty. + if ($this->onBufferDrain) { + try { + call_user_func($this->onBufferDrain, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + if ($this->_status === self::STATUS_CLOSING) { + $this->destroy(); + } + return true; + } + if ($len > 0) { + $this->bytesWritten += $len; + $this->_sendBuffer = substr($this->_sendBuffer, $len); + } else { + self::$statistics['send_fail']++; + $this->destroy(); + } + } + + /** + * SSL handshake. + * + * @param $socket + * @return bool + */ + public function doSslHandshake($socket){ + if (feof($socket)) { + $this->destroy(); + return false; + } + $async = $this instanceof AsyncTcpConnection; + + /** + * We disabled ssl3 because https://blog.qualys.com/ssllabs/2014/10/15/ssl-3-is-dead-killed-by-the-poodle-attack. + * You can enable ssl3 by the codes below. + */ + /*if($async){ + $type = STREAM_CRYPTO_METHOD_SSLv2_CLIENT | STREAM_CRYPTO_METHOD_SSLv23_CLIENT | STREAM_CRYPTO_METHOD_SSLv3_CLIENT; + }else{ + $type = STREAM_CRYPTO_METHOD_SSLv2_SERVER | STREAM_CRYPTO_METHOD_SSLv23_SERVER | STREAM_CRYPTO_METHOD_SSLv3_SERVER; + }*/ + + if($async){ + $type = STREAM_CRYPTO_METHOD_SSLv2_CLIENT | STREAM_CRYPTO_METHOD_SSLv23_CLIENT; + }else{ + $type = STREAM_CRYPTO_METHOD_SSLv2_SERVER | STREAM_CRYPTO_METHOD_SSLv23_SERVER; + } + + // Hidden error. + set_error_handler(function($errno, $errstr, $file){ + if (!Worker::$daemonize) { + Worker::safeEcho("SSL handshake error: $errstr \n"); + } + }); + $ret = stream_socket_enable_crypto($socket, true, $type); + restore_error_handler(); + // Negotiation has failed. + if (false === $ret) { + $this->destroy(); + return false; + } elseif (0 === $ret) { + // There isn't enough data and should try again. + return 0; + } + if (isset($this->onSslHandshake)) { + try { + call_user_func($this->onSslHandshake, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return true; + } + + /** + * This method pulls all the data out of a readable stream, and writes it to the supplied destination. + * + * @param TcpConnection $dest + * @return void + */ + public function pipe($dest) + { + $source = $this; + $this->onMessage = function ($source, $data) use ($dest) { + $dest->send($data); + }; + $this->onClose = function ($source) use ($dest) { + $dest->destroy(); + }; + $dest->onBufferFull = function ($dest) use ($source) { + $source->pauseRecv(); + }; + $dest->onBufferDrain = function ($dest) use ($source) { + $source->resumeRecv(); + }; + } + + /** + * Remove $length of data from receive buffer. + * + * @param int $length + * @return void + */ + public function consumeRecvBuffer($length) + { + $this->_recvBuffer = substr($this->_recvBuffer, $length); + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return void + */ + public function close($data = null, $raw = false) + { + if($this->_status === self::STATUS_CONNECTING){ + $this->destroy(); + return; + } + if ($this->_status === self::STATUS_CLOSING || $this->_status === self::STATUS_CLOSED) { + return; + } else { + if ($data !== null) { + $this->send($data, $raw); + } + $this->_status = self::STATUS_CLOSING; + } + if ($this->_sendBuffer === '') { + $this->destroy(); + } else { + $this->pauseRecv(); + } + } + + /** + * Get the real socket. + * + * @return resource + */ + public function getSocket() + { + return $this->_socket; + } + + /** + * Check whether the send buffer will be full. + * + * @return void + */ + protected function checkBufferWillFull() + { + if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) { + if ($this->onBufferFull) { + try { + call_user_func($this->onBufferFull, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + } + + /** + * Whether send buffer is full. + * + * @return bool + */ + protected function bufferIsFull() + { + // Buffer has been marked as full but still has data to send then the packet is discarded. + if ($this->maxSendBufferSize <= strlen($this->_sendBuffer)) { + if ($this->onError) { + try { + call_user_func($this->onError, $this, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return true; + } + return false; + } + + /** + * Whether send buffer is Empty. + * + * @return bool + */ + public function bufferIsEmpty() + { + return empty($this->_sendBuffer); + } + + /** + * Destroy connection. + * + * @return void + */ + public function destroy() + { + // Avoid repeated calls. + if ($this->_status === self::STATUS_CLOSED) { + return; + } + // Remove event listener. + Worker::$globalEvent->del($this->_socket, EventInterface::EV_READ); + Worker::$globalEvent->del($this->_socket, EventInterface::EV_WRITE); + + // Close socket. + set_error_handler(function(){}); + fclose($this->_socket); + restore_error_handler(); + + $this->_status = self::STATUS_CLOSED; + // Try to emit onClose callback. + if ($this->onClose) { + try { + call_user_func($this->onClose, $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Try to emit protocol::onClose + if ($this->protocol && method_exists($this->protocol, 'onClose')) { + try { + call_user_func(array($this->protocol, 'onClose'), $this); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $this->_sendBuffer = $this->_recvBuffer = ''; + if ($this->_status === self::STATUS_CLOSED) { + // Cleaning up the callback to avoid memory leaks. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferFull = $this->onBufferDrain = null; + // Remove from worker->connections. + if ($this->worker) { + unset($this->worker->connections[$this->_id]); + } + unset(static::$connections[$this->_id]); + } + } + + /** + * Destruct. + * + * @return void + */ + public function __destruct() + { + static $mod; + self::$statistics['connection_count']--; + if (Worker::getGracefulStop()) { + if (!isset($mod)) { + $mod = ceil((self::$statistics['connection_count'] + 1) / 3); + } + + if (0 === self::$statistics['connection_count'] % $mod) { + Worker::log('worker[' . posix_getpid() . '] remains ' . self::$statistics['connection_count'] . ' connection(s)'); + } + + if(0 === self::$statistics['connection_count']) { + Worker::stopAll(); + } + } + } +} diff --git a/bot/workerman/Connection/UdpConnection.php b/bot/workerman/Connection/UdpConnection.php new file mode 100755 index 0000000..2e7aeee --- /dev/null +++ b/bot/workerman/Connection/UdpConnection.php @@ -0,0 +1,191 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Connection; + +/** + * UdpConnection. + */ +class UdpConnection extends ConnectionInterface +{ + /** + * Application layer protocol. + * The format is like this Workerman\\Protocols\\Http. + * + * @var \Workerman\Protocols\ProtocolInterface + */ + public $protocol = null; + + /** + * Udp socket. + * + * @var resource + */ + protected $_socket = null; + + /** + * Remote address. + * + * @var string + */ + protected $_remoteAddress = ''; + + /** + * Construct. + * + * @param resource $socket + * @param string $remote_address + */ + public function __construct($socket, $remote_address) + { + $this->_socket = $socket; + $this->_remoteAddress = $remote_address; + } + + /** + * Sends data on the connection. + * + * @param string $send_buffer + * @param bool $raw + * @return void|boolean + */ + public function send($send_buffer, $raw = false) + { + if (false === $raw && $this->protocol) { + $parser = $this->protocol; + $send_buffer = $parser::encode($send_buffer, $this); + if ($send_buffer === '') { + return null; + } + } + return strlen($send_buffer) === stream_socket_sendto($this->_socket, $send_buffer, 0, $this->_remoteAddress); + } + + /** + * Get remote IP. + * + * @return string + */ + public function getRemoteIp() + { + $pos = strrpos($this->_remoteAddress, ':'); + if ($pos) { + return trim(substr($this->_remoteAddress, 0, $pos), '[]'); + } + return ''; + } + + /** + * Get remote port. + * + * @return int + */ + public function getRemotePort() + { + if ($this->_remoteAddress) { + return (int)substr(strrchr($this->_remoteAddress, ':'), 1); + } + return 0; + } + + /** + * Get remote address. + * + * @return string + */ + public function getRemoteAddress() + { + return $this->_remoteAddress; + } + + /** + * Get local IP. + * + * @return string + */ + public function getLocalIp() + { + $address = $this->getLocalAddress(); + $pos = strrpos($address, ':'); + if (!$pos) { + return ''; + } + return substr($address, 0, $pos); + } + + /** + * Get local port. + * + * @return int + */ + public function getLocalPort() + { + $address = $this->getLocalAddress(); + $pos = strrpos($address, ':'); + if (!$pos) { + return 0; + } + return (int)substr(strrchr($address, ':'), 1); + } + + /** + * Get local address. + * + * @return string + */ + public function getLocalAddress() + { + return (string)@stream_socket_get_name($this->_socket, false); + } + + /** + * Is ipv4. + * + * return bool. + */ + public function isIpV4() + { + if ($this->transport === 'unix') { + return false; + } + return strpos($this->getRemoteIp(), ':') === false; + } + + /** + * Is ipv6. + * + * return bool. + */ + public function isIpV6() + { + if ($this->transport === 'unix') { + return false; + } + return strpos($this->getRemoteIp(), ':') !== false; + } + + /** + * Close connection. + * + * @param mixed $data + * @param bool $raw + * @return bool + */ + public function close($data = null, $raw = false) + { + if ($data !== null) { + $this->send($data, $raw); + } + return true; + } +} diff --git a/bot/workerman/Events/Ev.php b/bot/workerman/Events/Ev.php new file mode 100755 index 0000000..1e6471d --- /dev/null +++ b/bot/workerman/Events/Ev.php @@ -0,0 +1,194 @@ + + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * ev eventloop + */ +class Ev implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected static $_timerId = 1; + + /** + * Add a timer. + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = null) + { + $callback = function ($event, $socket) use ($fd, $func) { + try { + call_user_func($func, $fd); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + }; + switch ($flag) { + case self::EV_SIGNAL: + $event = new \EvSignal($fd, $callback); + $this->_eventSignal[$fd] = $event; + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $repeat = $flag == self::EV_TIMER_ONCE ? 0 : $fd; + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new \EvTimer($fd, $repeat, array($this, 'timerCallback'), $param); + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? \Ev::READ : \Ev::WRITE; + $event = new \EvIo($fd, $real_flag, $callback); + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + + } + + /** + * Remove a timer. + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->stop(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->stop(); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->stop(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param \EvWatcher $event + */ + public function timerCallback($event) + { + $param = $event->data; + $timer_id = $param[4]; + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->stop(); + unset($this->_eventTimer[$timer_id]); + } + try { + call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->stop(); + } + $this->_eventTimer = array(); + } + + /** + * Main loop. + * + * @see EventInterface::loop() + */ + public function loop() + { + \Ev::run(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_allEvents as $event) { + $event->stop(); + } + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return count($this->_eventTimer); + } +} diff --git a/bot/workerman/Events/Event.php b/bot/workerman/Events/Event.php new file mode 100755 index 0000000..b4d371a --- /dev/null +++ b/bot/workerman/Events/Event.php @@ -0,0 +1,219 @@ + + * @copyright 有个鬼<42765633@qq.com> + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Event implements EventInterface +{ + /** + * Event base. + * @var object + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * @var int + */ + protected static $_timerId = 1; + + /** + * construct + * @return void + */ + public function __construct() + { + if (class_exists('\\\\EventBase', false)) { + $class_name = '\\\\EventBase'; + } else { + $class_name = '\EventBase'; + } + $this->_eventBase = new $class_name(); + } + + /** + * @see EventInterface::add() + */ + public function add($fd, $flag, $func, $args=array()) + { + if (class_exists('\\\\Event', false)) { + $class_name = '\\\\Event'; + } else { + $class_name = '\Event'; + } + switch ($flag) { + case self::EV_SIGNAL: + + $fd_key = (int)$fd; + $event = $class_name::signal($this->_eventBase, $fd, $func); + if (!$event||!$event->add()) { + return false; + } + $this->_eventSignal[$fd_key] = $event; + return true; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + + $param = array($func, (array)$args, $flag, $fd, self::$_timerId); + $event = new $class_name($this->_eventBase, -1, $class_name::TIMEOUT|$class_name::PERSIST, array($this, "timerCallback"), $param); + if (!$event||!$event->addTimer($fd)) { + return false; + } + $this->_eventTimer[self::$_timerId] = $event; + return self::$_timerId++; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? $class_name::READ | $class_name::PERSIST : $class_name::WRITE | $class_name::PERSIST; + $event = new $class_name($this->_eventBase, $fd, $real_flag, $func, $fd); + if (!$event||!$event->add()) { + return false; + } + $this->_allEvents[$fd_key][$flag] = $event; + return true; + } + } + + /** + * @see Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + + case self::EV_READ: + case self::EV_WRITE: + + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + $this->_allEvents[$fd_key][$flag]->del(); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + $this->_eventSignal[$fd_key]->del(); + unset($this->_eventSignal[$fd_key]); + } + break; + + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + if (isset($this->_eventTimer[$fd])) { + $this->_eventTimer[$fd]->del(); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * @param null $fd + * @param int $what + * @param int $timer_id + */ + public function timerCallback($fd, $what, $param) + { + $timer_id = $param[4]; + + if ($param[2] === self::EV_TIMER_ONCE) { + $this->_eventTimer[$timer_id]->del(); + unset($this->_eventTimer[$timer_id]); + } + + try { + call_user_func_array($param[0], $param[1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + + /** + * @see Events\EventInterface::clearAllTimer() + * @return void + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $event) { + $event->del(); + } + $this->_eventTimer = array(); + } + + + /** + * @see EventInterface::loop() + */ + public function loop() + { + $this->_eventBase->loop(); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_eventSignal as $event) { + $event->del(); + } + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return count($this->_eventTimer); + } +} diff --git a/bot/workerman/Events/EventInterface.php b/bot/workerman/Events/EventInterface.php new file mode 100755 index 0000000..88f38f2 --- /dev/null +++ b/bot/workerman/Events/EventInterface.php @@ -0,0 +1,107 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +interface EventInterface +{ + /** + * Read event. + * + * @var int + */ + const EV_READ = 1; + + /** + * Write event. + * + * @var int + */ + const EV_WRITE = 2; + + /** + * Except event + * + * @var int + */ + const EV_EXCEPT = 3; + + /** + * Signal event. + * + * @var int + */ + const EV_SIGNAL = 4; + + /** + * Timer event. + * + * @var int + */ + const EV_TIMER = 8; + + /** + * Timer once event. + * + * @var int + */ + const EV_TIMER_ONCE = 16; + + /** + * Add event listener to event loop. + * + * @param mixed $fd + * @param int $flag + * @param callable $func + * @param mixed $args + * @return bool + */ + public function add($fd, $flag, $func, $args = null); + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag); + + /** + * Remove all timers. + * + * @return void + */ + public function clearAllTimer(); + + /** + * Main loop. + * + * @return void + */ + public function loop(); + + /** + * Destroy loop. + * + * @return mixed + */ + public function destroy(); + + /** + * Get Timer count. + * + * @return mixed + */ + public function getTimerCount(); +} diff --git a/bot/workerman/Events/Libevent.php b/bot/workerman/Events/Libevent.php new file mode 100755 index 0000000..0b3f7c0 --- /dev/null +++ b/bot/workerman/Events/Libevent.php @@ -0,0 +1,227 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Workerman\Worker; + +/** + * libevent eventloop + */ +class Libevent implements EventInterface +{ + /** + * Event base. + * + * @var resource + */ + protected $_eventBase = null; + + /** + * All listeners for read/write event. + * + * @var array + */ + protected $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + protected $_eventSignal = array(); + + /** + * All timer event listeners. + * [func, args, event, flag, time_interval] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * construct + */ + public function __construct() + { + $this->_eventBase = event_base_new(); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_SIGNAL: + $fd_key = (int)$fd; + $real_flag = EV_SIGNAL | EV_PERSIST; + $this->_eventSignal[$fd_key] = event_new(); + if (!event_set($this->_eventSignal[$fd_key], $fd, $real_flag, $func, null)) { + return false; + } + if (!event_base_set($this->_eventSignal[$fd_key], $this->_eventBase)) { + return false; + } + if (!event_add($this->_eventSignal[$fd_key])) { + return false; + } + return true; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $event = event_new(); + $timer_id = (int)$event; + if (!event_set($event, 0, EV_TIMEOUT, array($this, 'timerCallback'), $timer_id)) { + return false; + } + + if (!event_base_set($event, $this->_eventBase)) { + return false; + } + + $time_interval = $fd * 1000000; + if (!event_add($event, $time_interval)) { + return false; + } + $this->_eventTimer[$timer_id] = array($func, (array)$args, $event, $flag, $time_interval); + return $timer_id; + + default : + $fd_key = (int)$fd; + $real_flag = $flag === self::EV_READ ? EV_READ | EV_PERSIST : EV_WRITE | EV_PERSIST; + + $event = event_new(); + + if (!event_set($event, $fd, $real_flag, $func, null)) { + return false; + } + + if (!event_base_set($event, $this->_eventBase)) { + return false; + } + + if (!event_add($event)) { + return false; + } + + $this->_allEvents[$fd_key][$flag] = $event; + + return true; + } + + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][$flag])) { + event_del($this->_allEvents[$fd_key][$flag]); + unset($this->_allEvents[$fd_key][$flag]); + } + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + break; + case self::EV_SIGNAL: + $fd_key = (int)$fd; + if (isset($this->_eventSignal[$fd_key])) { + event_del($this->_eventSignal[$fd_key]); + unset($this->_eventSignal[$fd_key]); + } + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + // 这里 fd 为timerid + if (isset($this->_eventTimer[$fd])) { + event_del($this->_eventTimer[$fd][2]); + unset($this->_eventTimer[$fd]); + } + break; + } + return true; + } + + /** + * Timer callback. + * + * @param mixed $_null1 + * @param int $_null2 + * @param mixed $timer_id + */ + protected function timerCallback($_null1, $_null2, $timer_id) + { + if ($this->_eventTimer[$timer_id][3] === self::EV_TIMER) { + event_add($this->_eventTimer[$timer_id][2], $this->_eventTimer[$timer_id][4]); + } + try { + call_user_func_array($this->_eventTimer[$timer_id][0], $this->_eventTimer[$timer_id][1]); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + if (isset($this->_eventTimer[$timer_id]) && $this->_eventTimer[$timer_id][3] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + foreach ($this->_eventTimer as $task_data) { + event_del($task_data[2]); + } + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + event_base_loop($this->_eventBase); + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + foreach ($this->_eventSignal as $event) { + event_del($event); + } + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return count($this->_eventTimer); + } +} + diff --git a/bot/workerman/Events/React/Base.php b/bot/workerman/Events/React/Base.php new file mode 100755 index 0000000..d558b88 --- /dev/null +++ b/bot/workerman/Events/React/Base.php @@ -0,0 +1,262 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; +use Workerman\Events\EventInterface; +use React\EventLoop\TimerInterface; + +/** + * Class StreamSelectLoop + * @package Workerman\Events\React + */ +class Base implements \React\EventLoop\LoopInterface +{ + /** + * @var array + */ + protected $_timerIdMap = array(); + + /** + * @var int + */ + protected $_timerIdIndex = 0; + + /** + * @var array + */ + protected $_signalHandlerMap = array(); + + /** + * @var \React\EventLoop\LoopInterface + */ + protected $_eventLoop = null; + + /** + * Base constructor. + */ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\StreamSelectLoop(); + } + + /** + * Add event listener to event loop. + * + * @param $fd + * @param $flag + * @param $func + * @param array $args + * @return bool + */ + public function add($fd, $flag, $func, $args = array()) + { + $args = (array)$args; + switch ($flag) { + case EventInterface::EV_READ: + return $this->addReadStream($fd, $func); + case EventInterface::EV_WRITE: + return $this->addWriteStream($fd, $func); + case EventInterface::EV_SIGNAL: + if (isset($this->_signalHandlerMap[$fd])) { + $this->removeSignal($fd, $this->_signalHandlerMap[$fd]); + } + $this->_signalHandlerMap[$fd] = $func; + return $this->addSignal($fd, $func); + case EventInterface::EV_TIMER: + $timer_obj = $this->addPeriodicTimer($fd, function() use ($func, $args) { + call_user_func_array($func, $args); + }); + $this->_timerIdMap[++$this->_timerIdIndex] = $timer_obj; + return $this->_timerIdIndex; + case EventInterface::EV_TIMER_ONCE: + $index = ++$this->_timerIdIndex; + $timer_obj = $this->addTimer($fd, function() use ($func, $args, $index) { + $this->del($index,EventInterface::EV_TIMER_ONCE); + call_user_func_array($func, $args); + }); + $this->_timerIdMap[$index] = $timer_obj; + return $this->_timerIdIndex; + } + return false; + } + + /** + * Remove event listener from event loop. + * + * @param mixed $fd + * @param int $flag + * @return bool + */ + public function del($fd, $flag) + { + switch ($flag) { + case EventInterface::EV_READ: + return $this->removeReadStream($fd); + case EventInterface::EV_WRITE: + return $this->removeWriteStream($fd); + case EventInterface::EV_SIGNAL: + if (!isset($this->_eventLoop[$fd])) { + return false; + } + $func = $this->_eventLoop[$fd]; + unset($this->_eventLoop[$fd]); + return $this->removeSignal($fd, $func); + + case EventInterface::EV_TIMER: + case EventInterface::EV_TIMER_ONCE: + if (isset($this->_timerIdMap[$fd])){ + $timer_obj = $this->_timerIdMap[$fd]; + unset($this->_timerIdMap[$fd]); + $this->cancelTimer($timer_obj); + return true; + } + } + return false; + } + + + /** + * Main loop. + * + * @return void + */ + public function loop() + { + $this->run(); + } + + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return count($this->_timerIdMap); + } + + /** + * @param resource $stream + * @param callable $listener + */ + public function addReadStream($stream, $listener) + { + return $this->_eventLoop->addReadStream($stream, $listener); + } + + /** + * @param resource $stream + * @param callable $listener + */ + public function addWriteStream($stream, $listener) + { + return $this->_eventLoop->addWriteStream($stream, $listener); + } + + /** + * @param resource $stream + */ + public function removeReadStream($stream) + { + return $this->_eventLoop->removeReadStream($stream); + } + + /** + * @param resource $stream + */ + public function removeWriteStream($stream) + { + return $this->_eventLoop->removeWriteStream($stream); + } + + /** + * @param float|int $interval + * @param callable $callback + * @return \React\EventLoop\Timer\Timer|TimerInterface + */ + public function addTimer($interval, $callback) + { + return $this->_eventLoop->addTimer($interval, $callback); + } + + /** + * @param float|int $interval + * @param callable $callback + * @return \React\EventLoop\Timer\Timer|TimerInterface + */ + public function addPeriodicTimer($interval, $callback) + { + return $this->_eventLoop->addPeriodicTimer($interval, $callback); + } + + /** + * @param TimerInterface $timer + */ + public function cancelTimer(TimerInterface $timer) + { + return $this->_eventLoop->cancelTimer($timer); + } + + /** + * @param callable $listener + */ + public function futureTick($listener) + { + return $this->_eventLoop->futureTick($listener); + } + + /** + * @param int $signal + * @param callable $listener + */ + public function addSignal($signal, $listener) + { + return $this->_eventLoop->addSignal($signal, $listener); + } + + /** + * @param int $signal + * @param callable $listener + */ + public function removeSignal($signal, $listener) + { + return $this->_eventLoop->removeSignal($signal, $listener); + } + + /** + * Run. + */ + public function run() + { + return $this->_eventLoop->run(); + } + + /** + * Stop. + */ + public function stop() + { + return $this->_eventLoop->stop(); + } +} diff --git a/bot/workerman/Events/React/ExtEventLoop.php b/bot/workerman/Events/React/ExtEventLoop.php new file mode 100755 index 0000000..3dab25b --- /dev/null +++ b/bot/workerman/Events/React/ExtEventLoop.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +/** + * Class ExtEventLoop + * @package Workerman\Events\React + */ +class ExtEventLoop extends Base +{ + + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\ExtEventLoop(); + } +} diff --git a/bot/workerman/Events/React/ExtLibEventLoop.php b/bot/workerman/Events/React/ExtLibEventLoop.php new file mode 100755 index 0000000..eb02b35 --- /dev/null +++ b/bot/workerman/Events/React/ExtLibEventLoop.php @@ -0,0 +1,27 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; +use Workerman\Events\EventInterface; + +/** + * Class ExtLibEventLoop + * @package Workerman\Events\React + */ +class ExtLibEventLoop extends Base +{ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\ExtLibeventLoop(); + } +} diff --git a/bot/workerman/Events/React/StreamSelectLoop.php b/bot/workerman/Events/React/StreamSelectLoop.php new file mode 100755 index 0000000..7f5f94b --- /dev/null +++ b/bot/workerman/Events/React/StreamSelectLoop.php @@ -0,0 +1,26 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events\React; + +/** + * Class StreamSelectLoop + * @package Workerman\Events\React + */ +class StreamSelectLoop extends Base +{ + public function __construct() + { + $this->_eventLoop = new \React\EventLoop\StreamSelectLoop(); + } +} diff --git a/bot/workerman/Events/Select.php b/bot/workerman/Events/Select.php new file mode 100755 index 0000000..801586b --- /dev/null +++ b/bot/workerman/Events/Select.php @@ -0,0 +1,340 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +/** + * select eventloop + */ +class Select implements EventInterface +{ + /** + * All listeners for read/write event. + * + * @var array + */ + public $_allEvents = array(); + + /** + * Event listeners of signal. + * + * @var array + */ + public $_signalEvents = array(); + + /** + * Fds waiting for read event. + * + * @var array + */ + protected $_readFds = array(); + + /** + * Fds waiting for write event. + * + * @var array + */ + protected $_writeFds = array(); + + /** + * Fds waiting for except event. + * + * @var array + */ + protected $_exceptFds = array(); + + /** + * Timer scheduler. + * {['data':timer_id, 'priority':run_timestamp], ..} + * + * @var \SplPriorityQueue + */ + protected $_scheduler = null; + + /** + * All timer event listeners. + * [[func, args, flag, timer_interval], ..] + * + * @var array + */ + protected $_eventTimer = array(); + + /** + * Timer id. + * + * @var int + */ + protected $_timerId = 1; + + /** + * Select timeout. + * + * @var int + */ + protected $_selectTimeout = 100000000; + + /** + * Paired socket channels + * + * @var array + */ + protected $channel = array(); + + /** + * Construct. + */ + public function __construct() + { + // Create a pipeline and put into the collection of the read to read the descriptor to avoid empty polling. + $this->channel = stream_socket_pair(DIRECTORY_SEPARATOR === '/' ? STREAM_PF_UNIX : STREAM_PF_INET, + STREAM_SOCK_STREAM, STREAM_IPPROTO_IP); + if($this->channel) { + stream_set_blocking($this->channel[0], 0); + $this->_readFds[0] = $this->channel[0]; + } + // Init SplPriorityQueue. + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + } + + /** + * {@inheritdoc} + */ + public function add($fd, $flag, $func, $args = array()) + { + switch ($flag) { + case self::EV_READ: + case self::EV_WRITE: + $count = $flag === self::EV_READ ? count($this->_readFds) : count($this->_writeFds); + if ($count >= 1024) { + echo "Warning: system call select exceeded the maximum number of connections 1024, please install event/libevent extension for more connections.\n"; + } else if (DIRECTORY_SEPARATOR !== '/' && $count >= 256) { + echo "Warning: system call select exceeded the maximum number of connections 256.\n"; + } + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + if ($flag === self::EV_READ) { + $this->_readFds[$fd_key] = $fd; + } else { + $this->_writeFds[$fd_key] = $fd; + } + break; + case self::EV_EXCEPT: + $fd_key = (int)$fd; + $this->_allEvents[$fd_key][$flag] = array($func, $fd); + $this->_exceptFds[$fd_key] = $fd; + break; + case self::EV_SIGNAL: + // Windows not support signal. + if(DIRECTORY_SEPARATOR !== '/') { + return false; + } + $fd_key = (int)$fd; + $this->_signalEvents[$fd_key][$flag] = array($func, $fd); + pcntl_signal($fd, array($this, 'signalHandler')); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $timer_id = $this->_timerId++; + $run_time = microtime(true) + $fd; + $this->_scheduler->insert($timer_id, -$run_time); + $this->_eventTimer[$timer_id] = array($func, (array)$args, $flag, $fd); + $select_timeout = ($run_time - microtime(true)) * 1000000; + if( $this->_selectTimeout > $select_timeout ){ + $this->_selectTimeout = $select_timeout; + } + return $timer_id; + } + + return true; + } + + /** + * Signal handler. + * + * @param int $signal + */ + public function signalHandler($signal) + { + call_user_func_array($this->_signalEvents[$signal][self::EV_SIGNAL][0], array($signal)); + } + + /** + * {@inheritdoc} + */ + public function del($fd, $flag) + { + $fd_key = (int)$fd; + switch ($flag) { + case self::EV_READ: + unset($this->_allEvents[$fd_key][$flag], $this->_readFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_WRITE: + unset($this->_allEvents[$fd_key][$flag], $this->_writeFds[$fd_key]); + if (empty($this->_allEvents[$fd_key])) { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_EXCEPT: + unset($this->_allEvents[$fd_key][$flag], $this->_exceptFds[$fd_key]); + if(empty($this->_allEvents[$fd_key])) + { + unset($this->_allEvents[$fd_key]); + } + return true; + case self::EV_SIGNAL: + if(DIRECTORY_SEPARATOR !== '/') { + return false; + } + unset($this->_signalEvents[$fd_key]); + pcntl_signal($fd, SIG_IGN); + break; + case self::EV_TIMER: + case self::EV_TIMER_ONCE; + unset($this->_eventTimer[$fd_key]); + return true; + } + return false; + } + + /** + * Tick for timer. + * + * @return void + */ + protected function tick() + { + while (!$this->_scheduler->isEmpty()) { + $scheduler_data = $this->_scheduler->top(); + $timer_id = $scheduler_data['data']; + $next_run_time = -$scheduler_data['priority']; + $time_now = microtime(true); + $this->_selectTimeout = ($next_run_time - $time_now) * 1000000; + if ($this->_selectTimeout <= 0) { + $this->_scheduler->extract(); + + if (!isset($this->_eventTimer[$timer_id])) { + continue; + } + + // [func, args, flag, timer_interval] + $task_data = $this->_eventTimer[$timer_id]; + if ($task_data[2] === self::EV_TIMER) { + $next_run_time = $time_now + $task_data[3]; + $this->_scheduler->insert($timer_id, -$next_run_time); + } + call_user_func_array($task_data[0], $task_data[1]); + if (isset($this->_eventTimer[$timer_id]) && $task_data[2] === self::EV_TIMER_ONCE) { + $this->del($timer_id, self::EV_TIMER_ONCE); + } + continue; + } + return; + } + $this->_selectTimeout = 100000000; + } + + /** + * {@inheritdoc} + */ + public function clearAllTimer() + { + $this->_scheduler = new \SplPriorityQueue(); + $this->_scheduler->setExtractFlags(\SplPriorityQueue::EXTR_BOTH); + $this->_eventTimer = array(); + } + + /** + * {@inheritdoc} + */ + public function loop() + { + while (1) { + if(DIRECTORY_SEPARATOR === '/') { + // Calls signal handlers for pending signals + pcntl_signal_dispatch(); + } + + $read = $this->_readFds; + $write = $this->_writeFds; + $except = $this->_exceptFds; + + // Waiting read/write/signal/timeout events. + set_error_handler(function(){}); + $ret = stream_select($read, $write, $except, 0, $this->_selectTimeout); + restore_error_handler(); + + + if (!$this->_scheduler->isEmpty()) { + $this->tick(); + } + + if (!$ret) { + continue; + } + + if ($read) { + foreach ($read as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_READ])) { + call_user_func_array($this->_allEvents[$fd_key][self::EV_READ][0], + array($this->_allEvents[$fd_key][self::EV_READ][1])); + } + } + } + + if ($write) { + foreach ($write as $fd) { + $fd_key = (int)$fd; + if (isset($this->_allEvents[$fd_key][self::EV_WRITE])) { + call_user_func_array($this->_allEvents[$fd_key][self::EV_WRITE][0], + array($this->_allEvents[$fd_key][self::EV_WRITE][1])); + } + } + } + + if($except) { + foreach($except as $fd) { + $fd_key = (int) $fd; + if(isset($this->_allEvents[$fd_key][self::EV_EXCEPT])) { + call_user_func_array($this->_allEvents[$fd_key][self::EV_EXCEPT][0], + array($this->_allEvents[$fd_key][self::EV_EXCEPT][1])); + } + } + } + } + } + + /** + * Destroy loop. + * + * @return void + */ + public function destroy() + { + + } + + /** + * Get timer count. + * + * @return integer + */ + public function getTimerCount() + { + return count($this->_eventTimer); + } +} diff --git a/bot/workerman/Events/Swoole.php b/bot/workerman/Events/Swoole.php new file mode 100755 index 0000000..40ef499 --- /dev/null +++ b/bot/workerman/Events/Swoole.php @@ -0,0 +1,221 @@ + + * @link http://www.workerman.net/ + * @link https://github.com/ares333/Workerman + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Events; + +use Swoole\Event; +use Swoole\Timer; + +class Swoole implements EventInterface +{ + + protected $_timer = array(); + + protected $_timerOnceMap = array(); + + protected $mapId = 0; + + protected $_fd = array(); + + // milisecond + public static $signalDispatchInterval = 200; + + protected $_hasSignal = false; + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::add() + */ + public function add($fd, $flag, $func, $args = null) + { + if (! isset($args)) { + $args = array(); + } + switch ($flag) { + case self::EV_SIGNAL: + $res = pcntl_signal($fd, $func, false); + if (! $this->_hasSignal && $res) { + Timer::tick(static::$signalDispatchInterval, + function () { + pcntl_signal_dispatch(); + }); + $this->_hasSignal = true; + } + return $res; + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + $method = self::EV_TIMER == $flag ? 'tick' : 'after'; + if ($this->mapId > PHP_INT_MAX) { + $this->mapId = 0; + } + $mapId = $this->mapId++; + $timer_id = Timer::$method($fd * 1000, + function ($timer_id = null) use ($func, $args, $mapId) { + call_user_func_array($func, $args); + // EV_TIMER_ONCE + if (! isset($timer_id)) { + // may be deleted in $func + if (array_key_exists($mapId, $this->_timerOnceMap)) { + $timer_id = $this->_timerOnceMap[$mapId]; + unset($this->_timer[$timer_id], + $this->_timerOnceMap[$mapId]); + } + } + }); + if ($flag == self::EV_TIMER_ONCE) { + $this->_timerOnceMap[$mapId] = $timer_id; + $this->_timer[$timer_id] = $mapId; + } else { + $this->_timer[$timer_id] = null; + } + return $timer_id; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int) $fd; + if (! isset($this->_fd[$fd_key])) { + if ($flag == self::EV_READ) { + $res = Event::add($fd, $func, null, SWOOLE_EVENT_READ); + $fd_type = SWOOLE_EVENT_READ; + } else { + $res = Event::add($fd, null, $func, SWOOLE_EVENT_WRITE); + $fd_type = SWOOLE_EVENT_WRITE; + } + if ($res) { + $this->_fd[$fd_key] = $fd_type; + } + } else { + $fd_val = $this->_fd[$fd_key]; + $res = true; + if ($flag == self::EV_READ) { + if (($fd_val & SWOOLE_EVENT_READ) != SWOOLE_EVENT_READ) { + $res = Event::set($fd, $func, null, + SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + $this->_fd[$fd_key] |= SWOOLE_EVENT_READ; + } + } else { + if (($fd_val & SWOOLE_EVENT_WRITE) != SWOOLE_EVENT_WRITE) { + $res = Event::set($fd, null, $func, + SWOOLE_EVENT_READ | SWOOLE_EVENT_WRITE); + $this->_fd[$fd_key] |= SWOOLE_EVENT_WRITE; + } + } + } + return $res; + } + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::del() + */ + public function del($fd, $flag) + { + switch ($flag) { + case self::EV_SIGNAL: + return pcntl_signal($fd, SIG_IGN, false); + case self::EV_TIMER: + case self::EV_TIMER_ONCE: + // already remove in EV_TIMER_ONCE callback. + if (! array_key_exists($fd, $this->_timer)) { + return true; + } + $res = Timer::clear($fd); + if ($res) { + $mapId = $this->_timer[$fd]; + if (isset($mapId)) { + unset($this->_timerOnceMap[$mapId]); + } + unset($this->_timer[$fd]); + } + return $res; + case self::EV_READ: + case self::EV_WRITE: + $fd_key = (int) $fd; + if (isset($this->_fd[$fd_key])) { + $fd_val = $this->_fd[$fd_key]; + if ($flag == self::EV_READ) { + $flag_remove = ~ SWOOLE_EVENT_READ; + } else { + $flag_remove = ~ SWOOLE_EVENT_WRITE; + } + $fd_val &= $flag_remove; + if (0 === $fd_val) { + $res = Event::del($fd); + if ($res) { + unset($this->_fd[$fd_key]); + } + } else { + $res = Event::set($fd, null, null, $fd_val); + if ($res) { + $this->_fd[$fd_key] = $fd_val; + } + } + } else { + $res = true; + } + return $res; + } + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::clearAllTimer() + */ + public function clearAllTimer() + { + foreach (array_keys($this->_timer) as $v) { + Timer::clear($v); + } + $this->_timer = array(); + $this->_timerOnceMap = array(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::loop() + */ + public function loop() + { + Event::wait(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::destroy() + */ + public function destroy() + { + //Event::exit(); + } + + /** + * + * {@inheritdoc} + * + * @see \Workerman\Events\EventInterface::getTimerCount() + */ + public function getTimerCount() + { + return count($this->_timer); + } +} diff --git a/bot/workerman/Lib/Constants.php b/bot/workerman/Lib/Constants.php new file mode 100755 index 0000000..1b3561b --- /dev/null +++ b/bot/workerman/Lib/Constants.php @@ -0,0 +1,40 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +// Display errors. +ini_set('display_errors', 'on'); +// Reporting all. +error_reporting(E_ALL); + +// Reset opcache. +if (function_exists('opcache_reset')) { + opcache_reset(); +} + +// For onError callback. +define('WORKERMAN_CONNECT_FAIL', 1); +// For onError callback. +define('WORKERMAN_SEND_FAIL', 2); + +// Define OS Type +define('OS_TYPE_LINUX', 'linux'); +define('OS_TYPE_WINDOWS', 'windows'); + +// Compatible with php7 +if(!class_exists('Error')) +{ + class Error extends Exception + { + } +} diff --git a/bot/workerman/Lib/Db.php b/bot/workerman/Lib/Db.php new file mode 100644 index 0000000..771d36d --- /dev/null +++ b/bot/workerman/Lib/Db.php @@ -0,0 +1,139 @@ +db = new \PDO($dsn, $data['dbuser'], $data['dbpassword']); + $this->db->query('set character set utf8mb4;'); + $this->db->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); + } + + public static function get($key = ''): Db + { + $database['dbname'] = Tool::ini($key . 'DB_DATABASE'); + $database['dbuser'] = Tool::ini($key . 'DB_USERNAME'); + $database['dbpassword'] = Tool::ini($key . 'DB_PASSWORD'); + $database['dbhost'] = Tool::ini($key . 'DB_HOST'); + if (!isset(self::$instance[$database['dbname']]) || !self::$instance[$database['dbname']] instanceof self) { + self::$instance[$database['dbname']] = new Db($database); + } else { + try { + //断线重连 + self::$instance[$database['dbname']]->db->getAttribute(\PDO::ATTR_SERVER_INFO); + } catch (\PDOException $e) { + if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) { + self::$instance[$database['dbname']] = new Db($database); + } + } + } + return self::$instance[$database['dbname']]; + } + + + /*********PDO**********/ + public function count($sql, $parameters = null): int + { + return $this->exeupdate($sql, $parameters); + } + + public function querysql($sql, $parameters = null) + { + return $this->exeupdate($sql, $parameters); + } + + + public function querysqlinsertid($sql, $parameters = null) + { + if ($this->exeupdate($sql, $parameters)) { + return $this->db->lastInsertId(); + } else { + return 0; + } + } + + + public function getRow($sql, $parameters = null) + { + $res = $this->exequery($sql, $parameters); + if (count($res) > 0) { + + return $res[0]; + } else { + return array(); + } + } + + public function getAll($sql, $parameters = null) + { + $res = $this->exequery($sql, $parameters); + if (count($res) > 0) { + return $res; + } else { + return array(); + } + } + + public function beginTransaction() + { + $this->db->beginTransaction(); + } + + public function rollback() + { + $this->db->rollback(); + } + + public function commit() + { + $this->db->commit(); + } + + public function exequery($sql, $parameters = null) + { + $conn = $this->db; + $stmt = $conn->prepare($sql); + $stmt->execute($parameters); + $rs = $stmt->fetchall(\PDO::FETCH_ASSOC); + $stmt = null; + $conn = null; + return $rs; + } + + public function exeupdate($sql, $parameters = null) + { + $stmt = $this->db->prepare($sql); + $stmt->execute($parameters); + $affectedrows = $stmt->rowcount(); + $stmt = null; + $conn = null; + return $affectedrows; + } + + public function checklink() + { + $res = $this->db->getAttribute(\PDO::ATTR_SERVER_INFO); + if (strpos($res, 'server has gone away') !== false) { + $this->db = null; + return false; + } else { + return true; + } + } + + public function getinsertid() + { + return $this->db->lastInsertId(); + } + + public function close() + { + return $this->db = null; + } +} diff --git a/bot/workerman/Lib/Db2.php b/bot/workerman/Lib/Db2.php new file mode 100644 index 0000000..1a96255 --- /dev/null +++ b/bot/workerman/Lib/Db2.php @@ -0,0 +1,43 @@ + $value) { + $sql .= "`{$key}`,"; + $sql1 .= "?,"; + $insertArr[] = $value; + } + $sql = trim($sql, ','); + $sql1 = trim($sql1, ','); + $sql .= ")" . $sql1 . ")"; + $db->querysql($sql, $insertArr); + return $db->getinsertid(); + } + + public static function u($db, $table, $array, $where, $where_a = []) + { + $updateArr = array(); + $sql = "update `{$table}` set "; + foreach ($array as $key => $value) { + $sql .= "`{$key}`=?,"; + $updateArr[] = $value; + } + $sql = trim($sql, ','); + $where = ' ' . $where; + $sql .= $where; + $updateArr = array_merge($updateArr, $where_a); + return $db->querysql($sql, $updateArr); + } + + public static function d($db, $table, $where, $where_a = []) + { + $sql = "delete from `{$table}` " . $where; + $db->querysql($sql, $where_a); + } +} \ No newline at end of file diff --git a/bot/workerman/Lib/Timer.php b/bot/workerman/Lib/Timer.php new file mode 100755 index 0000000..2dc2302 --- /dev/null +++ b/bot/workerman/Lib/Timer.php @@ -0,0 +1,179 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Lib; + +use Workerman\Events\EventInterface; +use Workerman\Worker; +use Exception; + +/** + * Timer. + * + * example: + * Workerman\Lib\Timer::add($time_interval, callback, array($arg1, $arg2..)); + */ +class Timer +{ + /** + * Tasks that based on ALARM signal. + * [ + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * run_time => [[$func, $args, $persistent, time_interval],[$func, $args, $persistent, time_interval],..]], + * .. + * ] + * + * @var array + */ + protected static $_tasks = array(); + + /** + * event + * + * @var \Workerman\Events\EventInterface + */ + protected static $_event = null; + + /** + * Init. + * + * @param \Workerman\Events\EventInterface $event + * @return void + */ + public static function init($event = null) + { + if ($event) { + self::$_event = $event; + } else { + if (function_exists('pcntl_signal')) { + pcntl_signal(SIGALRM, array('\Workerman\Lib\Timer', 'signalHandle'), false); + } + } + } + + /** + * ALARM signal handler. + * + * @return void + */ + public static function signalHandle() + { + if (!self::$_event) { + pcntl_alarm(1); + self::tick(); + } + } + + /** + * Add a timer. + * + * @param float $time_interval + * @param callable $func + * @param mixed $args + * @param bool $persistent + * @return int/false + */ + public static function add($time_interval, $func, $args = array(), $persistent = true) + { + if ($time_interval <= 0) { + Worker::safeEcho(new Exception("bad time_interval")); + return false; + } + + if (self::$_event) { + return self::$_event->add($time_interval, + $persistent ? EventInterface::EV_TIMER : EventInterface::EV_TIMER_ONCE, $func, $args); + } + + if (!is_callable($func)) { + Worker::safeEcho(new Exception("not callable")); + return false; + } + + if (empty(self::$_tasks)) { + pcntl_alarm(1); + } + + $time_now = time(); + $run_time = $time_now + $time_interval; + if (!isset(self::$_tasks[$run_time])) { + self::$_tasks[$run_time] = array(); + } + self::$_tasks[$run_time][] = array($func, (array)$args, $persistent, $time_interval); + return 1; + } + + + /** + * Tick. + * + * @return void + */ + public static function tick() + { + if (empty(self::$_tasks)) { + pcntl_alarm(0); + return; + } + + $time_now = time(); + foreach (self::$_tasks as $run_time => $task_data) { + if ($time_now >= $run_time) { + foreach ($task_data as $index => $one_task) { + $task_func = $one_task[0]; + $task_args = $one_task[1]; + $persistent = $one_task[2]; + $time_interval = $one_task[3]; + try { + call_user_func_array($task_func, $task_args); + } catch (\Exception $e) { + Worker::safeEcho($e); + } + if ($persistent) { + self::add($time_interval, $task_func, $task_args); + } + } + unset(self::$_tasks[$run_time]); + } + } + } + + /** + * Remove a timer. + * + * @param mixed $timer_id + * @return bool + */ + public static function del($timer_id) + { + if (self::$_event) { + return self::$_event->del($timer_id, EventInterface::EV_TIMER); + } + + return false; + } + + /** + * Remove all timers. + * + * @return void + */ + public static function delAll() + { + self::$_tasks = array(); + pcntl_alarm(0); + if (self::$_event) { + self::$_event->clearAllTimer(); + } + } +} diff --git a/bot/workerman/Lib/Tool.php b/bot/workerman/Lib/Tool.php new file mode 100644 index 0000000..35be164 --- /dev/null +++ b/bot/workerman/Lib/Tool.php @@ -0,0 +1,89 @@ + and contributors (see https://github.com/walkor/workerman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/bot/workerman/Protocols/Frame.php b/bot/workerman/Protocols/Frame.php new file mode 100755 index 0000000..4a6b13e --- /dev/null +++ b/bot/workerman/Protocols/Frame.php @@ -0,0 +1,61 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; + +/** + * Frame Protocol. + */ +class Frame +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($buffer, TcpConnection $connection) + { + if (strlen($buffer) < 4) { + return 0; + } + $unpack_data = unpack('Ntotal_length', $buffer); + return $unpack_data['total_length']; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + return substr($buffer, 4); + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + $total_length = 4 + strlen($buffer); + return pack('N', $total_length) . $buffer; + } +} diff --git a/bot/workerman/Protocols/Http.php b/bot/workerman/Protocols/Http.php new file mode 100755 index 0000000..12f7a2f --- /dev/null +++ b/bot/workerman/Protocols/Http.php @@ -0,0 +1,701 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; +use Workerman\Worker; + +/** + * http protocol + */ +class Http +{ + /** + * The supported HTTP methods + * @var array + */ + public static $methods = array('GET', 'POST', 'PUT', 'DELETE', 'HEAD', 'OPTIONS'); + + /** + * Check the integrity of the package. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($recv_buffer, TcpConnection $connection) + { + if (!strpos($recv_buffer, "\r\n\r\n")) { + // Judge whether the package length exceeds the limit. + if (strlen($recv_buffer) >= $connection->maxPackageSize) { + $connection->close(); + return 0; + } + return 0; + } + + list($header,) = explode("\r\n\r\n", $recv_buffer, 2); + $method = substr($header, 0, strpos($header, ' ')); + + if(in_array($method, static::$methods)) { + return static::getRequestSize($header, $method); + }else{ + $connection->send("HTTP/1.1 400 Bad Request\r\n\r\n", true); + return 0; + } + } + + /** + * Get whole size of the request + * includes the request headers and request body. + * @param string $header The request headers + * @param string $method The request method + * @return integer + */ + protected static function getRequestSize($header, $method) + { + if($method === 'GET' || $method === 'OPTIONS' || $method === 'HEAD') { + return strlen($header) + 4; + } + $match = array(); + if (preg_match("/\r\nContent-Length: ?(\d+)/i", $header, $match)) { + $content_length = isset($match[1]) ? $match[1] : 0; + return $content_length + strlen($header) + 4; + } + return $method === 'DELETE' ? strlen($header) + 4 : 0; + } + + /** + * Parse $_POST、$_GET、$_COOKIE. + * + * @param string $recv_buffer + * @param TcpConnection $connection + * @return array + */ + public static function decode($recv_buffer, TcpConnection $connection) + { + // Init. + $_POST = $_GET = $_COOKIE = $_REQUEST = $_SESSION = $_FILES = array(); + $GLOBALS['HTTP_RAW_POST_DATA'] = ''; + // Clear cache. + HttpCache::$header = array('Connection' => 'Connection: keep-alive'); + HttpCache::$instance = new HttpCache(); + // $_SERVER + $_SERVER = array( + 'QUERY_STRING' => '', + 'REQUEST_METHOD' => '', + 'REQUEST_URI' => '', + 'SERVER_PROTOCOL' => '', + 'SERVER_SOFTWARE' => 'workerman/'.Worker::VERSION, + 'SERVER_NAME' => '', + 'HTTP_HOST' => '', + 'HTTP_USER_AGENT' => '', + 'HTTP_ACCEPT' => '', + 'HTTP_ACCEPT_LANGUAGE' => '', + 'HTTP_ACCEPT_ENCODING' => '', + 'HTTP_COOKIE' => '', + 'HTTP_CONNECTION' => '', + 'CONTENT_TYPE' => '', + 'REMOTE_ADDR' => '', + 'REMOTE_PORT' => '0', + 'REQUEST_TIME' => time() + ); + + // Parse headers. + list($http_header, $http_body) = explode("\r\n\r\n", $recv_buffer, 2); + $header_data = explode("\r\n", $http_header); + + list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', + $header_data[0]); + + $http_post_boundary = ''; + unset($header_data[0]); + foreach ($header_data as $content) { + // \r\n\r\n + if (empty($content)) { + continue; + } + list($key, $value) = explode(':', $content, 2); + $key = str_replace('-', '_', strtoupper($key)); + $value = trim($value); + $_SERVER['HTTP_' . $key] = $value; + switch ($key) { + // HTTP_HOST + case 'HOST': + $tmp = explode(':', $value); + $_SERVER['SERVER_NAME'] = $tmp[0]; + if (isset($tmp[1])) { + $_SERVER['SERVER_PORT'] = $tmp[1]; + } + break; + // cookie + case 'COOKIE': + parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); + break; + // content-type + case 'CONTENT_TYPE': + if (!preg_match('/boundary="?(\S+)"?/', $value, $match)) { + if ($pos = strpos($value, ';')) { + $_SERVER['CONTENT_TYPE'] = substr($value, 0, $pos); + } else { + $_SERVER['CONTENT_TYPE'] = $value; + } + } else { + $_SERVER['CONTENT_TYPE'] = 'multipart/form-data'; + $http_post_boundary = '--' . $match[1]; + } + break; + case 'CONTENT_LENGTH': + $_SERVER['CONTENT_LENGTH'] = $value; + break; + case 'UPGRADE': + if($value=='websocket'){ + $connection->protocol = "\\Workerman\\Protocols\\Websocket"; + return \Workerman\Protocols\Websocket::input($recv_buffer,$connection); + } + break; + } + } + if(isset($_SERVER['HTTP_ACCEPT_ENCODING']) && strpos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip') !== FALSE){ + HttpCache::$gzip = true; + } + // Parse $_POST. + if ($_SERVER['REQUEST_METHOD'] === 'POST') { + if (isset($_SERVER['CONTENT_TYPE'])) { + switch ($_SERVER['CONTENT_TYPE']) { + case 'multipart/form-data': + self::parseUploadFiles($http_body, $http_post_boundary); + break; + case 'application/json': + $_POST = json_decode($http_body, true); + break; + case 'application/x-www-form-urlencoded': + parse_str($http_body, $_POST); + break; + } + } + } + + // Parse other HTTP action parameters + if ($_SERVER['REQUEST_METHOD'] != 'GET' && $_SERVER['REQUEST_METHOD'] != "POST") { + $data = array(); + if ($_SERVER['CONTENT_TYPE'] === "application/x-www-form-urlencoded") { + parse_str($http_body, $data); + } elseif ($_SERVER['CONTENT_TYPE'] === "application/json") { + $data = json_decode($http_body, true); + } + $_REQUEST = array_merge($_REQUEST, $data); + } + + // HTTP_RAW_REQUEST_DATA HTTP_RAW_POST_DATA + $GLOBALS['HTTP_RAW_REQUEST_DATA'] = $GLOBALS['HTTP_RAW_POST_DATA'] = $http_body; + + // QUERY_STRING + $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); + if ($_SERVER['QUERY_STRING']) { + // $GET + parse_str($_SERVER['QUERY_STRING'], $_GET); + } else { + $_SERVER['QUERY_STRING'] = ''; + } + + if (is_array($_POST)) { + // REQUEST + $_REQUEST = array_merge($_GET, $_POST, $_REQUEST); + } else { + // REQUEST + $_REQUEST = array_merge($_GET, $_REQUEST); + } + + // REMOTE_ADDR REMOTE_PORT + $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); + $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); + + return array('get' => $_GET, 'post' => $_POST, 'cookie' => $_COOKIE, 'server' => $_SERVER, 'files' => $_FILES); + } + + /** + * Http encode. + * + * @param string $content + * @param TcpConnection $connection + * @return string + */ + public static function encode($content, TcpConnection $connection) + { + // Default http-code. + if (!isset(HttpCache::$header['Http-Code'])) { + $header = "HTTP/1.1 200 OK\r\n"; + } else { + $header = HttpCache::$header['Http-Code'] . "\r\n"; + unset(HttpCache::$header['Http-Code']); + } + + // Content-Type + if (!isset(HttpCache::$header['Content-Type'])) { + $header .= "Content-Type: text/html;charset=utf-8\r\n"; + } + + // other headers + foreach (HttpCache::$header as $key => $item) { + if ('Set-Cookie' === $key && is_array($item)) { + foreach ($item as $it) { + $header .= $it . "\r\n"; + } + } else { + $header .= $item . "\r\n"; + } + } + if(HttpCache::$gzip && isset($connection->gzip) && $connection->gzip){ + $header .= "Content-Encoding: gzip\r\n"; + $content = gzencode($content,$connection->gzip); + } + // header + $header .= "Server: workerman/" . Worker::VERSION . "\r\nContent-Length: " . strlen($content) . "\r\n\r\n"; + + // save session + self::sessionWriteClose(); + + // the whole http package + return $header . $content; + } + + /** + * 设置http头 + * + * @return bool|void + */ + public static function header($content, $replace = true, $http_response_code = 0) + { + if (PHP_SAPI != 'cli') { + return $http_response_code ? header($content, $replace, $http_response_code) : header($content, $replace); + } + if (strpos($content, 'HTTP') === 0) { + $key = 'Http-Code'; + } else { + $key = strstr($content, ":", true); + if (empty($key)) { + return false; + } + } + + if ('location' === strtolower($key) && !$http_response_code) { + return self::header($content, true, 302); + } + + if (isset(HttpCache::$codes[$http_response_code])) { + HttpCache::$header['Http-Code'] = "HTTP/1.1 $http_response_code " . HttpCache::$codes[$http_response_code]; + if ($key === 'Http-Code') { + return true; + } + } + + if ($key === 'Set-Cookie') { + HttpCache::$header[$key][] = $content; + } else { + HttpCache::$header[$key] = $content; + } + + return true; + } + + /** + * Remove header. + * + * @param string $name + * @return void + */ + public static function headerRemove($name) + { + if (PHP_SAPI != 'cli') { + header_remove($name); + return; + } + unset(HttpCache::$header[$name]); + } + + /** + * Set cookie. + * + * @param string $name + * @param string $value + * @param integer $maxage + * @param string $path + * @param string $domain + * @param bool $secure + * @param bool $HTTPOnly + * @return bool|void + */ + public static function setcookie( + $name, + $value = '', + $maxage = 0, + $path = '', + $domain = '', + $secure = false, + $HTTPOnly = false + ) { + if (PHP_SAPI != 'cli') { + return setcookie($name, $value, $maxage, $path, $domain, $secure, $HTTPOnly); + } + return self::header( + 'Set-Cookie: ' . $name . '=' . rawurlencode($value) + . (empty($domain) ? '' : '; Domain=' . $domain) + . (empty($maxage) ? '' : '; Max-Age=' . $maxage) + . (empty($path) ? '' : '; Path=' . $path) + . (!$secure ? '' : '; Secure') + . (!$HTTPOnly ? '' : '; HttpOnly'), false); + } + + /** + * sessionCreateId + * + * @return string + */ + public static function sessionCreateId() + { + mt_srand(); + return bin2hex(pack('d', microtime(true)) . pack('N',mt_rand(0, 2147483647))); + } + + /** + * sessionId + * + * @param string $id + * + * @return string|null + */ + public static function sessionId($id = null) + { + if (PHP_SAPI != 'cli') { + return $id ? session_id($id) : session_id(); + } + if (static::sessionStarted() && HttpCache::$instance->sessionFile) { + return str_replace('ses_', '', basename(HttpCache::$instance->sessionFile)); + } + return ''; + } + + /** + * sessionName + * + * @param string $name + * + * @return string + */ + public static function sessionName($name = null) + { + if (PHP_SAPI != 'cli') { + return $name ? session_name($name) : session_name(); + } + $session_name = HttpCache::$sessionName; + if ($name && ! static::sessionStarted()) { + HttpCache::$sessionName = $name; + } + return $session_name; + } + + /** + * sessionSavePath + * + * @param string $path + * + * @return void + */ + public static function sessionSavePath($path = null) + { + if (PHP_SAPI != 'cli') { + return $path ? session_save_path($path) : session_save_path(); + } + if ($path && is_dir($path) && is_writable($path) && !static::sessionStarted()) { + HttpCache::$sessionPath = $path; + } + return HttpCache::$sessionPath; + } + + /** + * sessionStarted + * + * @return bool + */ + public static function sessionStarted() + { + if (!HttpCache::$instance) return false; + + return HttpCache::$instance->sessionStarted; + } + + /** + * sessionStart + * + * @return bool + */ + public static function sessionStart() + { + if (PHP_SAPI != 'cli') { + return session_start(); + } + + self::tryGcSessions(); + + if (HttpCache::$instance->sessionStarted) { + Worker::safeEcho("already sessionStarted\n"); + return true; + } + HttpCache::$instance->sessionStarted = true; + // Generate a SID. + if (!isset($_COOKIE[HttpCache::$sessionName]) || !is_file(HttpCache::$sessionPath . '/ses_' . $_COOKIE[HttpCache::$sessionName])) { + // Create a unique session_id and the associated file name. + while (true) { + $session_id = static::sessionCreateId(); + if (!is_file($file_name = HttpCache::$sessionPath . '/ses_' . $session_id)) break; + } + HttpCache::$instance->sessionFile = $file_name; + return self::setcookie( + HttpCache::$sessionName + , $session_id + , ini_get('session.cookie_lifetime') + , ini_get('session.cookie_path') + , ini_get('session.cookie_domain') + , ini_get('session.cookie_secure') + , ini_get('session.cookie_httponly') + ); + } + if (!HttpCache::$instance->sessionFile) { + HttpCache::$instance->sessionFile = HttpCache::$sessionPath . '/ses_' . $_COOKIE[HttpCache::$sessionName]; + } + // Read session from session file. + if (HttpCache::$instance->sessionFile) { + $raw = file_get_contents(HttpCache::$instance->sessionFile); + if ($raw) { + $_SESSION = unserialize($raw); + } + } + return true; + } + + /** + * Save session. + * + * @return bool + */ + public static function sessionWriteClose() + { + if (PHP_SAPI != 'cli') { + return session_write_close(); + } + if (!empty(HttpCache::$instance->sessionStarted) && !empty($_SESSION)) { + $session_str = serialize($_SESSION); + if ($session_str && HttpCache::$instance->sessionFile) { + return file_put_contents(HttpCache::$instance->sessionFile, $session_str); + } + } + return empty($_SESSION); + } + + /** + * End, like call exit in php-fpm. + * + * @param string $msg + * @throws \Exception + */ + public static function end($msg = '') + { + if (PHP_SAPI != 'cli') { + exit($msg); + } + if ($msg) { + echo $msg; + } + throw new \Exception('jump_exit'); + } + + /** + * Get mime types. + * + * @return string + */ + public static function getMimeTypesFile() + { + return __DIR__ . '/Http/mime.types'; + } + + /** + * Parse $_FILES. + * + * @param string $http_body + * @param string $http_post_boundary + * @return void + */ + protected static function parseUploadFiles($http_body, $http_post_boundary) + { + $http_body = substr($http_body, 0, strlen($http_body) - (strlen($http_post_boundary) + 4)); + $boundary_data_array = explode($http_post_boundary . "\r\n", $http_body); + if ($boundary_data_array[0] === '') { + unset($boundary_data_array[0]); + } + $key = -1; + foreach ($boundary_data_array as $boundary_data_buffer) { + list($boundary_header_buffer, $boundary_value) = explode("\r\n\r\n", $boundary_data_buffer, 2); + // Remove \r\n from the end of buffer. + $boundary_value = substr($boundary_value, 0, -2); + $key ++; + foreach (explode("\r\n", $boundary_header_buffer) as $item) { + list($header_key, $header_value) = explode(": ", $item); + $header_key = strtolower($header_key); + switch ($header_key) { + case "content-disposition": + // Is file data. + if (preg_match('/name="(.*?)"; filename="(.*?)"$/', $header_value, $match)) { + // Parse $_FILES. + $_FILES[$key] = array( + 'name' => $match[1], + 'file_name' => $match[2], + 'file_data' => $boundary_value, + 'file_size' => strlen($boundary_value), + ); + break; + } // Is post field. + else { + // Parse $_POST. + if (preg_match('/name="(.*?)"$/', $header_value, $match)) { + $_POST[$match[1]] = $boundary_value; + } + } + break; + case "content-type": + // add file_type + $_FILES[$key]['file_type'] = trim($header_value); + break; + } + } + } + } + + /** + * Try GC sessions. + * + * @return void + */ + public static function tryGcSessions() + { + if (HttpCache::$sessionGcProbability <= 0 || + HttpCache::$sessionGcDivisor <= 0 || + rand(1, HttpCache::$sessionGcDivisor) > HttpCache::$sessionGcProbability) { + return; + } + + $time_now = time(); + foreach(glob(HttpCache::$sessionPath.'/ses*') as $file) { + if(is_file($file) && $time_now - filemtime($file) > HttpCache::$sessionGcMaxLifeTime) { + unlink($file); + } + } + } +} + +/** + * Http cache for the current http response. + */ +class HttpCache +{ + public static $codes = array( + 100 => 'Continue', + 101 => 'Switching Protocols', + 200 => 'OK', + 201 => 'Created', + 202 => 'Accepted', + 203 => 'Non-Authoritative Information', + 204 => 'No Content', + 205 => 'Reset Content', + 206 => 'Partial Content', + 300 => 'Multiple Choices', + 301 => 'Moved Permanently', + 302 => 'Found', + 303 => 'See Other', + 304 => 'Not Modified', + 305 => 'Use Proxy', + 306 => '(Unused)', + 307 => 'Temporary Redirect', + 400 => 'Bad Request', + 401 => 'Unauthorized', + 402 => 'Payment Required', + 403 => 'Forbidden', + 404 => 'Not Found', + 405 => 'Method Not Allowed', + 406 => 'Not Acceptable', + 407 => 'Proxy Authentication Required', + 408 => 'Request Timeout', + 409 => 'Conflict', + 410 => 'Gone', + 411 => 'Length Required', + 412 => 'Precondition Failed', + 413 => 'Request Entity Too Large', + 414 => 'Request-URI Too Long', + 415 => 'Unsupported Media Type', + 416 => 'Requested Range Not Satisfiable', + 417 => 'Expectation Failed', + 422 => 'Unprocessable Entity', + 423 => 'Locked', + 500 => 'Internal Server Error', + 501 => 'Not Implemented', + 502 => 'Bad Gateway', + 503 => 'Service Unavailable', + 504 => 'Gateway Timeout', + 505 => 'HTTP Version Not Supported', + ); + + /** + * @var HttpCache + */ + public static $instance = null; + public static $header = array(); + public static $gzip = false; + public static $sessionPath = ''; + public static $sessionName = ''; + public static $sessionGcProbability = 1; + public static $sessionGcDivisor = 1000; + public static $sessionGcMaxLifeTime = 1440; + public $sessionStarted = false; + public $sessionFile = ''; + + public static function init() + { + if (!self::$sessionName) { + self::$sessionName = ini_get('session.name'); + } + + if (!self::$sessionPath) { + self::$sessionPath = @session_save_path(); + } + + if (!self::$sessionPath || strpos(self::$sessionPath, 'tcp://') === 0) { + self::$sessionPath = sys_get_temp_dir(); + } + + if ($gc_probability = ini_get('session.gc_probability')) { + self::$sessionGcProbability = $gc_probability; + } + + if ($gc_divisor = ini_get('session.gc_divisor')) { + self::$sessionGcDivisor = $gc_divisor; + } + + if ($gc_max_life_time = ini_get('session.gc_maxlifetime')) { + self::$sessionGcMaxLifeTime = $gc_max_life_time; + } + } +} + +HttpCache::init(); diff --git a/bot/workerman/Protocols/Http/mime.types b/bot/workerman/Protocols/Http/mime.types new file mode 100755 index 0000000..8a218b2 --- /dev/null +++ b/bot/workerman/Protocols/Http/mime.types @@ -0,0 +1,80 @@ + +types { + text/html html htm shtml; + text/css css; + text/xml xml; + image/gif gif; + image/jpeg jpeg jpg; + application/x-javascript js; + application/atom+xml atom; + application/rss+xml rss; + + text/mathml mml; + text/plain txt; + text/vnd.sun.j2me.app-descriptor jad; + text/vnd.wap.wml wml; + text/x-component htc; + + image/png png; + image/tiff tif tiff; + image/vnd.wap.wbmp wbmp; + image/x-icon ico; + image/x-jng jng; + image/x-ms-bmp bmp; + image/svg+xml svg svgz; + image/webp webp; + + application/java-archive jar war ear; + application/mac-binhex40 hqx; + application/msword doc; + application/pdf pdf; + application/postscript ps eps ai; + application/rtf rtf; + application/vnd.ms-excel xls; + application/vnd.ms-powerpoint ppt; + application/vnd.wap.wmlc wmlc; + application/vnd.google-earth.kml+xml kml; + application/vnd.google-earth.kmz kmz; + application/x-7z-compressed 7z; + application/x-cocoa cco; + application/x-java-archive-diff jardiff; + application/x-java-jnlp-file jnlp; + application/x-makeself run; + application/x-perl pl pm; + application/x-pilot prc pdb; + application/x-rar-compressed rar; + application/x-redhat-package-manager rpm; + application/x-sea sea; + application/x-shockwave-flash swf; + application/x-stuffit sit; + application/x-tcl tcl tk; + application/x-x509-ca-cert der pem crt; + application/x-xpinstall xpi; + application/xhtml+xml xhtml; + application/zip zip; + + application/octet-stream bin exe dll; + application/octet-stream deb; + application/octet-stream dmg; + application/octet-stream eot; + application/octet-stream iso img; + application/octet-stream msi msp msm; + + audio/midi mid midi kar; + audio/mpeg mp3; + audio/ogg ogg; + audio/x-m4a m4a; + audio/x-realaudio ra; + + video/3gpp 3gpp 3gp; + video/mp4 mp4; + video/mpeg mpeg mpg; + video/quicktime mov; + video/webm webm; + video/x-flv flv; + video/x-m4v m4v; + video/x-mng mng; + video/x-ms-asf asx asf; + video/x-ms-wmv wmv; + video/x-msvideo avi; +} diff --git a/bot/workerman/Protocols/ProtocolInterface.php b/bot/workerman/Protocols/ProtocolInterface.php new file mode 100755 index 0000000..9afe984 --- /dev/null +++ b/bot/workerman/Protocols/ProtocolInterface.php @@ -0,0 +1,52 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; + +/** + * Protocol interface + */ +interface ProtocolInterface +{ + /** + * Check the integrity of the package. + * Please return the length of package. + * If length is unknow please return 0 that mean wating more data. + * If the package has something wrong please return false the connection will be closed. + * + * @param ConnectionInterface $connection + * @param string $recv_buffer + * @return int|false + */ + public static function input($recv_buffer, ConnectionInterface $connection); + + /** + * Decode package and emit onMessage($message) callback, $message is the result that decode returned. + * + * @param ConnectionInterface $connection + * @param string $recv_buffer + * @return mixed + */ + public static function decode($recv_buffer, ConnectionInterface $connection); + + /** + * Encode package brefore sending to client. + * + * @param ConnectionInterface $connection + * @param mixed $data + * @return string + */ + public static function encode($data, ConnectionInterface $connection); +} diff --git a/bot/workerman/Protocols/Text.php b/bot/workerman/Protocols/Text.php new file mode 100755 index 0000000..84b937a --- /dev/null +++ b/bot/workerman/Protocols/Text.php @@ -0,0 +1,70 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\TcpConnection; + +/** + * Text Protocol. + */ +class Text +{ + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param TcpConnection $connection + * @return int + */ + public static function input($buffer, TcpConnection $connection) + { + // Judge whether the package length exceeds the limit. + if (strlen($buffer) >= $connection->maxPackageSize) { + $connection->close(); + return 0; + } + // Find the position of "\n". + $pos = strpos($buffer, "\n"); + // No "\n", packet length is unknown, continue to wait for the data so return 0. + if ($pos === false) { + return 0; + } + // Return the current package length. + return $pos + 1; + } + + /** + * Encode. + * + * @param string $buffer + * @return string + */ + public static function encode($buffer) + { + // Add "\n" + return $buffer . "\n"; + } + + /** + * Decode. + * + * @param string $buffer + * @return string + */ + public static function decode($buffer) + { + // Remove "\n" + return rtrim($buffer, "\r\n"); + } +} diff --git a/bot/workerman/Protocols/Websocket.php b/bot/workerman/Protocols/Websocket.php new file mode 100755 index 0000000..6237f21 --- /dev/null +++ b/bot/workerman/Protocols/Websocket.php @@ -0,0 +1,503 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Worker; + +/** + * WebSocket protocol. + */ +class Websocket implements \Workerman\Protocols\ProtocolInterface +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, ConnectionInterface $connection) + { + // Receive length. + $recv_len = strlen($buffer); + // We need more data. + if ($recv_len < 6) { + return 0; + } + + // Has not yet completed the handshake. + if (empty($connection->websocketHandshake)) { + return static::dealHandshake($buffer, $connection); + } + + // Buffer websocket frame data. + if ($connection->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->websocketCurrentFrameLength > $recv_len) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + $firstbyte = ord($buffer[0]); + $secondbyte = ord($buffer[1]); + $data_len = $secondbyte & 127; + $is_fin_frame = $firstbyte >> 7; + $masked = $secondbyte >> 7; + + if (!$masked) { + Worker::safeEcho("frame not masked so close the connection\n"); + $connection->close(); + return 0; + } + + $opcode = $firstbyte & 0xf; + switch ($opcode) { + case 0x0: + break; + // Blob type. + case 0x1: + break; + // Arraybuffer type. + case 0x2: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + if (isset($connection->onWebSocketClose) || isset($connection->worker->onWebSocketClose)) { + try { + call_user_func(isset($connection->onWebSocketClose)?$connection->onWebSocketClose:$connection->worker->onWebSocketClose, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Close connection. + else { + $connection->close("\x88\x02\x27\x10", true); + } + return 0; + // Ping package. + case 0x9: + break; + // Pong package. + case 0xa: + break; + // Wrong opcode. + default : + Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . bin2hex($buffer) . "\n"); + $connection->close(); + return 0; + } + + // Calculate packet length. + $head_len = 6; + if ($data_len === 126) { + $head_len = 8; + if ($head_len > $recv_len) { + return 0; + } + $pack = unpack('nn/ntotal_len', $buffer); + $data_len = $pack['total_len']; + } else { + if ($data_len === 127) { + $head_len = 14; + if ($head_len > $recv_len) { + return 0; + } + $arr = unpack('n/N2c', $buffer); + $data_len = $arr['c1']*4294967296 + $arr['c2']; + } + } + $current_frame_length = $head_len + $data_len; + + $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length; + if ($total_package_size > $connection->maxPackageSize) { + Worker::safeEcho("error package. package_length=$total_package_size\n"); + $connection->close(); + return 0; + } + + if ($is_fin_frame) { + if ($opcode === 0x9) { + if ($recv_len >= $current_frame_length) { + $ping_data = static::decode(substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + if (isset($connection->onWebSocketPing) || isset($connection->worker->onWebSocketPing)) { + try { + call_user_func(isset($connection->onWebSocketPing)?$connection->onWebSocketPing:$connection->worker->onWebSocketPing, $connection, $ping_data); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } else { + $connection->send($ping_data); + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } else if ($opcode === 0xa) { + if ($recv_len >= $current_frame_length) { + $pong_data = static::decode(substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + // Try to emit onWebSocketPong callback. + if (isset($connection->onWebSocketPong) || isset($connection->worker->onWebSocketPong)) { + try { + call_user_func(isset($connection->onWebSocketPong)?$connection->onWebSocketPong:$connection->worker->onWebSocketPong, $connection, $pong_data); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } + return $current_frame_length; + } else { + $connection->websocketCurrentFrameLength = $current_frame_length; + } + } + + // Received just a frame length data. + if ($connection->websocketCurrentFrameLength === $recv_len) { + static::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $connection->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->websocketCurrentFrameLength < $recv_len) { + static::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $current_frame_length = $connection->websocketCurrentFrameLength; + $connection->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return static::input(substr($buffer, $current_frame_length), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($buffer, ConnectionInterface $connection) + { + if (!is_scalar($buffer)) { + throw new \Exception("You can't send(" . gettype($buffer) . ") to client, you need to convert it to a string. "); + } + $len = strlen($buffer); + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + + $first_byte = $connection->websocketType; + + if ($len <= 125) { + $encode_buffer = $first_byte . chr($len) . $buffer; + } else { + if ($len <= 65535) { + $encode_buffer = $first_byte . chr(126) . pack("n", $len) . $buffer; + } else { + $encode_buffer = $first_byte . chr(127) . pack("xxxxN", $len) . $buffer; + } + } + + // Handshake not completed so temporary buffer websocket data waiting for send. + if (empty($connection->websocketHandshake)) { + if (empty($connection->tmpWebsocketData)) { + $connection->tmpWebsocketData = ''; + } + // If buffer has already full then discard the current package. + if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return ''; + } + $connection->tmpWebsocketData .= $encode_buffer; + // Check buffer is full. + if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + call_user_func($connection->onBufferFull, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + + // Return empty string. + return ''; + } + + return $encode_buffer; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($buffer, ConnectionInterface $connection) + { + $len = ord($buffer[1]) & 127; + if ($len === 126) { + $masks = substr($buffer, 4, 4); + $data = substr($buffer, 8); + } else { + if ($len === 127) { + $masks = substr($buffer, 10, 4); + $data = substr($buffer, 14); + } else { + $masks = substr($buffer, 2, 4); + $data = substr($buffer, 6); + } + } + $dataLength = strlen($data); + $masks = str_repeat($masks, floor($dataLength / 4)) . substr($masks, 0, $dataLength % 4); + $decoded = $data ^ $masks; + if ($connection->websocketCurrentFrameLength) { + $connection->websocketDataBuffer .= $decoded; + return $connection->websocketDataBuffer; + } else { + if ($connection->websocketDataBuffer !== '') { + $decoded = $connection->websocketDataBuffer . $decoded; + $connection->websocketDataBuffer = ''; + } + return $decoded; + } + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param \Workerman\Connection\TcpConnection $connection + * @return int + */ + protected static function dealHandshake($buffer, $connection) + { + // HTTP protocol. + if (0 === strpos($buffer, 'GET')) { + // Find \r\n\r\n. + $heder_end_pos = strpos($buffer, "\r\n\r\n"); + if (!$heder_end_pos) { + return 0; + } + $header_length = $heder_end_pos + 4; + + // Get Sec-WebSocket-Key. + $Sec_WebSocket_Key = ''; + if (preg_match("/Sec-WebSocket-Key: *(.*?)\r\n/i", $buffer, $match)) { + $Sec_WebSocket_Key = $match[1]; + } else { + $connection->send("HTTP/1.1 200 Websocket\r\nServer: workerman/".Worker::VERSION."\r\n\r\n", + true); + $connection->close(); + return 0; + } + // Calculation websocket key. + $new_key = base64_encode(sha1($Sec_WebSocket_Key . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true)); + // Handshake response data. + $handshake_message = "HTTP/1.1 101 Switching Protocols\r\n"; + $handshake_message .= "Upgrade: websocket\r\n"; + $handshake_message .= "Sec-WebSocket-Version: 13\r\n"; + $handshake_message .= "Connection: Upgrade\r\n"; + $handshake_message .= "Sec-WebSocket-Accept: " . $new_key . "\r\n"; + + // Websocket data buffer. + $connection->websocketDataBuffer = ''; + // Current websocket frame length. + $connection->websocketCurrentFrameLength = 0; + // Current websocket frame data. + $connection->websocketCurrentFrameBuffer = ''; + // Consume handshake data. + $connection->consumeRecvBuffer($header_length); + + // blob or arraybuffer + if (empty($connection->websocketType)) { + $connection->websocketType = static::BINARY_TYPE_BLOB; + } + + $has_server_header = false; + + // Try to emit onWebSocketConnect callback. + if (isset($connection->onWebSocketConnect) || isset($connection->worker->onWebSocketConnect)) { + static::parseHttpHeader($buffer); + try { + call_user_func(isset($connection->onWebSocketConnect)?$connection->onWebSocketConnect:$connection->worker->onWebSocketConnect, $connection, $buffer); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + if (!empty($_SESSION) && class_exists('\GatewayWorker\Lib\Context')) { + $connection->session = \GatewayWorker\Lib\Context::sessionEncode($_SESSION); + } + $_GET = $_SERVER = $_SESSION = $_COOKIE = array(); + + if (isset($connection->headers)) { + if (is_array($connection->headers)) { + foreach ($connection->headers as $header) { + if (strpos($header, 'Server:') === 0) { + $has_server_header = true; + } + $handshake_message .= "$header\r\n"; + } + } else { + $handshake_message .= "$connection->headers\r\n"; + } + } + } + if (!$has_server_header) { + $handshake_message .= "Server: workerman/".Worker::VERSION."\r\n"; + } + $handshake_message .= "\r\n"; + // Send handshake response. + $connection->send($handshake_message, true); + // Mark handshake complete.. + $connection->websocketHandshake = true; + // There are data waiting to be sent. + if (!empty($connection->tmpWebsocketData)) { + $connection->send($connection->tmpWebsocketData, true); + $connection->tmpWebsocketData = ''; + } + if (strlen($buffer) > $header_length) { + return static::input(substr($buffer, $header_length), $connection); + } + return 0; + } // Is flash policy-file-request. + elseif (0 === strpos($buffer, 'send($policy_xml, true); + $connection->consumeRecvBuffer(strlen($buffer)); + return 0; + } + // Bad websocket handshake request. + $connection->send("HTTP/1.1 200 Websocket\r\nServer: workerman/".Worker::VERSION."\r\n\r\n", + true); + $connection->close(); + return 0; + } + + /** + * Parse http header. + * + * @param string $buffer + * @return void + */ + protected static function parseHttpHeader($buffer) + { + // Parse headers. + list($http_header, ) = explode("\r\n\r\n", $buffer, 2); + $header_data = explode("\r\n", $http_header); + + if ($_SERVER) { + $_SERVER = array(); + } + + list($_SERVER['REQUEST_METHOD'], $_SERVER['REQUEST_URI'], $_SERVER['SERVER_PROTOCOL']) = explode(' ', + $header_data[0]); + + unset($header_data[0]); + foreach ($header_data as $content) { + // \r\n\r\n + if (empty($content)) { + continue; + } + list($key, $value) = explode(':', $content, 2); + $key = str_replace('-', '_', strtoupper($key)); + $value = trim($value); + $_SERVER['HTTP_' . $key] = $value; + switch ($key) { + // HTTP_HOST + case 'HOST': + $tmp = explode(':', $value); + $_SERVER['SERVER_NAME'] = $tmp[0]; + if (isset($tmp[1])) { + $_SERVER['SERVER_PORT'] = $tmp[1]; + } + break; + // cookie + case 'COOKIE': + parse_str(str_replace('; ', '&', $_SERVER['HTTP_COOKIE']), $_COOKIE); + break; + } + } + + // QUERY_STRING + $_SERVER['QUERY_STRING'] = parse_url($_SERVER['REQUEST_URI'], PHP_URL_QUERY); + if ($_SERVER['QUERY_STRING']) { + // $GET + parse_str($_SERVER['QUERY_STRING'], $_GET); + } else { + $_SERVER['QUERY_STRING'] = ''; + } + } +} diff --git a/bot/workerman/Protocols/Ws.php b/bot/workerman/Protocols/Ws.php new file mode 100755 index 0000000..e3ce4a6 --- /dev/null +++ b/bot/workerman/Protocols/Ws.php @@ -0,0 +1,471 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman\Protocols; + +use Workerman\Worker; +use Workerman\Lib\Timer; +use Workerman\Connection\TcpConnection; + +/** + * Websocket protocol for client. + */ +class Ws +{ + /** + * Websocket blob type. + * + * @var string + */ + const BINARY_TYPE_BLOB = "\x81"; + + /** + * Websocket arraybuffer type. + * + * @var string + */ + const BINARY_TYPE_ARRAYBUFFER = "\x82"; + + /** + * Check the integrity of the package. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return int + */ + public static function input($buffer, $connection) + { + if (empty($connection->handshakeStep)) { + Worker::safeEcho("recv data before handshake. Buffer:" . bin2hex($buffer) . "\n"); + return false; + } + // Recv handshake response + if ($connection->handshakeStep === 1) { + return self::dealHandshake($buffer, $connection); + } + $recv_len = strlen($buffer); + if ($recv_len < 2) { + return 0; + } + // Buffer websocket frame data. + if ($connection->websocketCurrentFrameLength) { + // We need more frame data. + if ($connection->websocketCurrentFrameLength > $recv_len) { + // Return 0, because it is not clear the full packet length, waiting for the frame of fin=1. + return 0; + } + } else { + + $firstbyte = ord($buffer[0]); + $secondbyte = ord($buffer[1]); + $data_len = $secondbyte & 127; + $is_fin_frame = $firstbyte >> 7; + $masked = $secondbyte >> 7; + + if ($masked) { + Worker::safeEcho("frame masked so close the connection\n"); + $connection->close(); + return 0; + } + + $opcode = $firstbyte & 0xf; + + switch ($opcode) { + case 0x0: + break; + // Blob type. + case 0x1: + break; + // Arraybuffer type. + case 0x2: + break; + // Close package. + case 0x8: + // Try to emit onWebSocketClose callback. + if (isset($connection->onWebSocketClose)) { + try { + call_user_func($connection->onWebSocketClose, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } // Close connection. + else { + $connection->close(); + } + return 0; + // Ping package. + case 0x9: + break; + // Pong package. + case 0xa: + break; + // Wrong opcode. + default : + Worker::safeEcho("error opcode $opcode and close websocket connection. Buffer:" . $buffer . "\n"); + $connection->close(); + return 0; + } + // Calculate packet length. + if ($data_len === 126) { + if (strlen($buffer) < 4) { + return 0; + } + $pack = unpack('nn/ntotal_len', $buffer); + $current_frame_length = $pack['total_len'] + 4; + } else if ($data_len === 127) { + if (strlen($buffer) < 10) { + return 0; + } + $arr = unpack('n/N2c', $buffer); + $current_frame_length = $arr['c1']*4294967296 + $arr['c2'] + 10; + } else { + $current_frame_length = $data_len + 2; + } + + $total_package_size = strlen($connection->websocketDataBuffer) + $current_frame_length; + if ($total_package_size > $connection->maxPackageSize) { + Worker::safeEcho("error package. package_length=$total_package_size\n"); + $connection->close(); + return 0; + } + + if ($is_fin_frame) { + if ($opcode === 0x9) { + if ($recv_len >= $current_frame_length) { + $ping_data = static::decode(substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + if (isset($connection->onWebSocketPing)) { + try { + call_user_func($connection->onWebSocketPing, $connection, $ping_data); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } else { + $connection->send($ping_data); + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(substr($buffer, $current_frame_length), $connection); + } + } + return 0; + + } else if ($opcode === 0xa) { + if ($recv_len >= $current_frame_length) { + $pong_data = static::decode(substr($buffer, 0, $current_frame_length), $connection); + $connection->consumeRecvBuffer($current_frame_length); + $tmp_connection_type = isset($connection->websocketType) ? $connection->websocketType : static::BINARY_TYPE_BLOB; + $connection->websocketType = "\x8a"; + // Try to emit onWebSocketPong callback. + if (isset($connection->onWebSocketPong)) { + try { + call_user_func($connection->onWebSocketPong, $connection, $pong_data); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + $connection->websocketType = $tmp_connection_type; + if ($recv_len > $current_frame_length) { + return static::input(substr($buffer, $current_frame_length), $connection); + } + } + return 0; + } + return $current_frame_length; + } else { + $connection->websocketCurrentFrameLength = $current_frame_length; + } + } + // Received just a frame length data. + if ($connection->websocketCurrentFrameLength === $recv_len) { + self::decode($buffer, $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $connection->websocketCurrentFrameLength = 0; + return 0; + } // The length of the received data is greater than the length of a frame. + elseif ($connection->websocketCurrentFrameLength < $recv_len) { + self::decode(substr($buffer, 0, $connection->websocketCurrentFrameLength), $connection); + $connection->consumeRecvBuffer($connection->websocketCurrentFrameLength); + $current_frame_length = $connection->websocketCurrentFrameLength; + $connection->websocketCurrentFrameLength = 0; + // Continue to read next frame. + return self::input(substr($buffer, $current_frame_length), $connection); + } // The length of the received data is less than the length of a frame. + else { + return 0; + } + } + + /** + * Websocket encode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function encode($payload, $connection) + { + if (empty($connection->websocketType)) { + $connection->websocketType = self::BINARY_TYPE_BLOB; + } + $payload = (string)$payload; + if (empty($connection->handshakeStep)) { + static::sendHandshake($connection); + } + $mask = 1; + $mask_key = "\x00\x00\x00\x00"; + + $pack = ''; + $length = $length_flag = strlen($payload); + if (65535 < $length) { + $pack = pack('NN', ($length & 0xFFFFFFFF00000000) >> 32, $length & 0x00000000FFFFFFFF); + $length_flag = 127; + } else if (125 < $length) { + $pack = pack('n*', $length); + $length_flag = 126; + } + + $head = ($mask << 7) | $length_flag; + $head = $connection->websocketType . chr($head) . $pack; + + $frame = $head . $mask_key; + // append payload to frame: + $mask_key = str_repeat($mask_key, floor($length / 4)) . substr($mask_key, 0, $length % 4); + $frame .= $payload ^ $mask_key; + if ($connection->handshakeStep === 1) { + // If buffer has already full then discard the current package. + if (strlen($connection->tmpWebsocketData) > $connection->maxSendBufferSize) { + if ($connection->onError) { + try { + call_user_func($connection->onError, $connection, WORKERMAN_SEND_FAIL, 'send buffer full and drop package'); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + return ''; + } + $connection->tmpWebsocketData = $connection->tmpWebsocketData . $frame; + // Check buffer is full. + if ($connection->maxSendBufferSize <= strlen($connection->tmpWebsocketData)) { + if ($connection->onBufferFull) { + try { + call_user_func($connection->onBufferFull, $connection); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + } + return ''; + } + return $frame; + } + + /** + * Websocket decode. + * + * @param string $buffer + * @param ConnectionInterface $connection + * @return string + */ + public static function decode($bytes, $connection) + { + $data_length = ord($bytes[1]); + + if ($data_length === 126) { + $decoded_data = substr($bytes, 4); + } else if ($data_length === 127) { + $decoded_data = substr($bytes, 10); + } else { + $decoded_data = substr($bytes, 2); + } + if ($connection->websocketCurrentFrameLength) { + $connection->websocketDataBuffer .= $decoded_data; + return $connection->websocketDataBuffer; + } else { + if ($connection->websocketDataBuffer !== '') { + $decoded_data = $connection->websocketDataBuffer . $decoded_data; + $connection->websocketDataBuffer = ''; + } + return $decoded_data; + } + } + + /** + * Send websocket handshake data. + * + * @return void + */ + public static function onConnect($connection) + { + static::sendHandshake($connection); + } + + /** + * Clean + * + * @param $connection + */ + public static function onClose($connection) + { + $connection->handshakeStep = null; + $connection->websocketCurrentFrameLength = 0; + $connection->tmpWebsocketData = ''; + $connection->websocketDataBuffer = ''; + if (!empty($connection->websocketPingTimer)) { + Timer::del($connection->websocketPingTimer); + $connection->websocketPingTimer = null; + } + } + + /** + * Send websocket handshake. + * + * @param \Workerman\Connection\TcpConnection $connection + * @return void + */ + public static function sendHandshake($connection) + { + if (!empty($connection->handshakeStep)) { + return; + } + // Get Host. + $port = $connection->getRemotePort(); + $host = $port === 80 ? $connection->getRemoteHost() : $connection->getRemoteHost() . ':' . $port; + // Handshake header. + $connection->websocketSecKey = base64_encode(md5(mt_rand(), true)); + $user_header = isset($connection->headers) ? $connection->headers : + (isset($connection->wsHttpHeader) ? $connection->wsHttpHeader : null); + $user_header_str = ''; + if (!empty($user_header)) { + if (is_array($user_header)){ + foreach($user_header as $k=>$v){ + $user_header_str .= "$k: $v\r\n"; + } + } else { + $user_header_str .= $user_header; + } + $user_header_str = "\r\n".trim($user_header_str); + } + $header = 'GET ' . $connection->getRemoteURI() . " HTTP/1.1\r\n". + (!preg_match("/\nHost:/i", $user_header_str) ? "Host: $host\r\n" : ''). + "Connection: Upgrade\r\n". + "Upgrade: websocket\r\n". + (isset($connection->websocketOrigin) ? "Origin: ".$connection->websocketOrigin."\r\n":''). + (isset($connection->WSClientProtocol)?"Sec-WebSocket-Protocol: ".$connection->WSClientProtocol."\r\n":''). + "Sec-WebSocket-Version: 13\r\n". + "Sec-WebSocket-Key: " . $connection->websocketSecKey . $user_header_str . "\r\n\r\n"; + $connection->send($header, true); + $connection->handshakeStep = 1; + $connection->websocketCurrentFrameLength = 0; + $connection->websocketDataBuffer = ''; + $connection->tmpWebsocketData = ''; + } + + /** + * Websocket handshake. + * + * @param string $buffer + * @param \Workerman\Connection\TcpConnection $connection + * @return int + */ + public static function dealHandshake($buffer, $connection) + { + $pos = strpos($buffer, "\r\n\r\n"); + if ($pos) { + //checking Sec-WebSocket-Accept + if (preg_match("/Sec-WebSocket-Accept: *(.*?)\r\n/i", $buffer, $match)) { + if ($match[1] !== base64_encode(sha1($connection->websocketSecKey . "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", true))) { + Worker::safeEcho("Sec-WebSocket-Accept not match. Header:\n" . substr($buffer, 0, $pos) . "\n"); + $connection->close(); + return 0; + } + } else { + Worker::safeEcho("Sec-WebSocket-Accept not found. Header:\n" . substr($buffer, 0, $pos) . "\n"); + $connection->close(); + return 0; + } + + // handshake complete + + // Get WebSocket subprotocol (if specified by server) + if (preg_match("/Sec-WebSocket-Protocol: *(.*?)\r\n/i", $buffer, $match)) { + $connection->WSServerProtocol = trim($match[1]); + } + + $connection->handshakeStep = 2; + $handshake_response_length = $pos + 4; + // Try to emit onWebSocketConnect callback. + if (isset($connection->onWebSocketConnect)) { + try { + call_user_func($connection->onWebSocketConnect, $connection, substr($buffer, 0, $handshake_response_length)); + } catch (\Exception $e) { + Worker::log($e); + exit(250); + } catch (\Error $e) { + Worker::log($e); + exit(250); + } + } + // Headbeat. + if (!empty($connection->websocketPingInterval)) { + $connection->websocketPingTimer = Timer::add($connection->websocketPingInterval, function() use ($connection){ + if (false === $connection->send(pack('H*', '898000000000'), true)) { + Timer::del($connection->websocketPingTimer); + $connection->websocketPingTimer = null; + } + }); + } + + $connection->consumeRecvBuffer($handshake_response_length); + if (!empty($connection->tmpWebsocketData)) { + $connection->send($connection->tmpWebsocketData, true); + $connection->tmpWebsocketData = ''; + } + if (strlen($buffer) > $handshake_response_length) { + return self::input(substr($buffer, $handshake_response_length), $connection); + } + } + return 0; + } + + public static function WSSetProtocol($connection, $params) { + $connection->WSClientProtocol = $params[0]; + } + + public static function WSGetServerProtocol($connection) { + return (property_exists($connection, 'WSServerProtocol')?$connection->WSServerProtocol:null); + } + +} diff --git a/bot/workerman/README.md b/bot/workerman/README.md new file mode 100755 index 0000000..33869e5 --- /dev/null +++ b/bot/workerman/README.md @@ -0,0 +1,604 @@ +# Workerman +[![Gitter](https://badges.gitter.im/walkor/Workerman.svg)](https://gitter.im/walkor/Workerman?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=body_badge) +[![Latest Stable Version](https://poser.pugx.org/workerman/workerman/v/stable)](https://packagist.org/packages/workerman/workerman) +[![Total Downloads](https://poser.pugx.org/workerman/workerman/downloads)](https://packagist.org/packages/workerman/workerman) +[![Monthly Downloads](https://poser.pugx.org/workerman/workerman/d/monthly)](https://packagist.org/packages/workerman/workerman) +[![Daily Downloads](https://poser.pugx.org/workerman/workerman/d/daily)](https://packagist.org/packages/workerman/workerman) +[![License](https://poser.pugx.org/workerman/workerman/license)](https://packagist.org/packages/workerman/workerman) + +## What is it +Workerman is an asynchronous event driven PHP framework with high performance for easily building fast, scalable network applications. Supports HTTP, Websocket, SSL and other custom protocols. Supports libevent/event extension, [HHVM](https://github.com/facebook/hhvm) , [ReactPHP](https://github.com/reactphp/react). + +## Requires +PHP 5.3 or Higher +A POSIX compatible operating system (Linux, OSX, BSD) +POSIX and PCNTL extensions required +Event extension recommended for better performance + +## Installation + +``` +composer require workerman/workerman +``` + +## Basic Usage + +### A websocket server +```php +count = 4; + +// Emitted when new connection come +$ws_worker->onConnect = function($connection) +{ + echo "New connection\n"; + }; + +// Emitted when data received +$ws_worker->onMessage = function($connection, $data) +{ + // Send hello $data + $connection->send('hello ' . $data); +}; + +// Emitted when connection closed +$ws_worker->onClose = function($connection) +{ + echo "Connection closed\n"; +}; + +// Run worker +Worker::runAll(); +``` + +### An http server +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; + +// #### http worker #### +$http_worker = new Worker("http://0.0.0.0:2345"); + +// 4 processes +$http_worker->count = 4; + +// Emitted when data received +$http_worker->onMessage = function($connection, $data) +{ + // $_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES are available + var_dump($_GET, $_POST, $_COOKIE, $_SESSION, $_SERVER, $_FILES); + // send data to client + $connection->send("hello world \n"); +}; + +// run all workers +Worker::runAll(); +``` + +### A WebServer +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\WebServer; +use Workerman\Worker; + +// WebServer +$web = new WebServer("http://0.0.0.0:80"); + +// 4 processes +$web->count = 4; + +// Set the root of domains +$web->addRoot('www.your_domain.com', '/your/path/Web'); +$web->addRoot('www.another_domain.com', '/another/path/Web'); +// run all workers +Worker::runAll(); +``` + +### A tcp server +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; + +// #### create socket and listen 1234 port #### +$tcp_worker = new Worker("tcp://0.0.0.0:1234"); + +// 4 processes +$tcp_worker->count = 4; + +// Emitted when new connection come +$tcp_worker->onConnect = function($connection) +{ + echo "New Connection\n"; +}; + +// Emitted when data received +$tcp_worker->onMessage = function($connection, $data) +{ + // send data to client + $connection->send("hello $data \n"); +}; + +// Emitted when new connection come +$tcp_worker->onClose = function($connection) +{ + echo "Connection closed\n"; +}; + +Worker::runAll(); +``` + +### Enable SSL +```php + array( + 'local_cert' => '/your/path/of/server.pem', + 'local_pk' => '/your/path/of/server.key', + 'verify_peer' => false, + ) +); + +// Create a Websocket server with ssl context. +$ws_worker = new Worker("websocket://0.0.0.0:2346", $context); + +// Enable SSL. WebSocket+SSL means that Secure WebSocket (wss://). +// The similar approaches for Https etc. +$ws_worker->transport = 'ssl'; + +$ws_worker->onMessage = function($connection, $data) +{ + // Send hello $data + $connection->send('hello ' . $data); +}; + +Worker::runAll(); +``` + +### Custom protocol +Protocols/MyTextProtocol.php +```php +namespace Protocols; +/** + * User defined protocol + * Format Text+"\n" + */ +class MyTextProtocol +{ + public static function input($recv_buffer) + { + // Find the position of the first occurrence of "\n" + $pos = strpos($recv_buffer, "\n"); + // Not a complete package. Return 0 because the length of package can not be calculated + if($pos === false) + { + return 0; + } + // Return length of the package + return $pos+1; + } + + public static function decode($recv_buffer) + { + return trim($recv_buffer); + } + + public static function encode($data) + { + return $data."\n"; + } +} +``` + +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; + +// #### MyTextProtocol worker #### +$text_worker = new Worker("MyTextProtocol://0.0.0.0:5678"); + +$text_worker->onConnect = function($connection) +{ + echo "New connection\n"; +}; + +$text_worker->onMessage = function($connection, $data) +{ + // send data to client + $connection->send("hello world \n"); +}; + +$text_worker->onClose = function($connection) +{ + echo "Connection closed\n"; +}; + +// run all workers +Worker::runAll(); +``` + +### Timer +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; +use Workerman\Lib\Timer; + +$task = new Worker(); +$task->onWorkerStart = function($task) +{ + // 2.5 seconds + $time_interval = 2.5; + $timer_id = Timer::add($time_interval, + function() + { + echo "Timer run\n"; + } + ); +}; + +// run all workers +Worker::runAll(); +``` + +### AsyncTcpConnection (tcp/ws/text/frame etc...) +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; +use Workerman\Connection\AsyncTcpConnection; + +$worker = new Worker(); +$worker->onWorkerStart = function() +{ + // Websocket protocol for client. + $ws_connection = new AsyncTcpConnection("ws://echo.websocket.org:80"); + $ws_connection->onConnect = function($connection){ + $connection->send('hello'); + }; + $ws_connection->onMessage = function($connection, $data){ + echo "recv: $data\n"; + }; + $ws_connection->onError = function($connection, $code, $msg){ + echo "error: $msg\n"; + }; + $ws_connection->onClose = function($connection){ + echo "connection closed\n"; + }; + $ws_connection->connect(); +}; +Worker::runAll(); +``` + +### Async Mysql of ReactPHP +``` +composer require react/mysql +``` + +```php +onWorkerStart = function() { + global $mysql; + $loop = Worker::getEventLoop(); + $mysql = new React\MySQL\Connection($loop, array( + 'host' => '127.0.0.1', + 'dbname' => 'dbname', + 'user' => 'user', + 'passwd' => 'passwd', + )); + $mysql->on('error', function($e){ + echo $e; + }); + $mysql->connect(function ($e) { + if($e) { + echo $e; + } else { + echo "connect success\n"; + } + }); +}; +$worker->onMessage = function($connection, $data) { + global $mysql; + $mysql->query('show databases' /*trim($data)*/, function ($command, $mysql) use ($connection) { + if ($command->hasError()) { + $error = $command->getError(); + } else { + $results = $command->resultRows; + $fields = $command->resultFields; + $connection->send(json_encode($results)); + } + }); +}; +Worker::runAll(); +``` + +### Async Redis of ReactPHP +``` +composer require clue/redis-react +``` + +```php +onWorkerStart = function() { + global $factory; + $loop = Worker::getEventLoop(); + $factory = new Factory($loop); +}; + +$worker->onMessage = function($connection, $data) { + global $factory; + $factory->createClient('localhost:6379')->then(function (Client $client) use ($connection) { + $client->set('greeting', 'Hello world'); + $client->append('greeting', '!'); + + $client->get('greeting')->then(function ($greeting) use ($connection){ + // Hello world! + echo $greeting . PHP_EOL; + $connection->send($greeting); + }); + + $client->incr('invocation')->then(function ($n) use ($connection){ + echo 'This is invocation #' . $n . PHP_EOL; + $connection->send($n); + }); + }); +}; + +Worker::runAll(); +``` + +### Aysnc dns of ReactPHP +``` +composer require react/dns +``` + +```php +require_once __DIR__ . '/vendor/autoload.php'; +use Workerman\Worker; +$worker = new Worker('tcp://0.0.0.0:6161'); +$worker->onWorkerStart = function() { + global $dns; + // Get event-loop. + $loop = Worker::getEventLoop(); + $factory = new React\Dns\Resolver\Factory(); + $dns = $factory->create('8.8.8.8', $loop); +}; +$worker->onMessage = function($connection, $host) { + global $dns; + $host = trim($host); + $dns->resolve($host)->then(function($ip) use($host, $connection) { + $connection->send("$host: $ip"); + },function($e) use($host, $connection){ + $connection->send("$host: {$e->getMessage()}"); + }); +}; + +Worker::runAll(); +``` + +### Http client of ReactPHP +``` +composer require react/http-client +``` + +```php +onMessage = function($connection, $host) { + $loop = Worker::getEventLoop(); + $client = new \React\HttpClient\Client($loop); + $request = $client->request('GET', trim($host)); + $request->on('error', function(Exception $e) use ($connection) { + $connection->send($e); + }); + $request->on('response', function ($response) use ($connection) { + $response->on('data', function ($data) use ($connection) { + $connection->send($data); + }); + }); + $request->end(); +}; + +Worker::runAll(); +``` + +### ZMQ of ReactPHP +``` +composer require react/zmq +``` + +```php +onWorkerStart = function() { + global $pull; + $loop = Worker::getEventLoop(); + $context = new React\ZMQ\Context($loop); + $pull = $context->getSocket(ZMQ::SOCKET_PULL); + $pull->bind('tcp://127.0.0.1:5555'); + + $pull->on('error', function ($e) { + var_dump($e->getMessage()); + }); + + $pull->on('message', function ($msg) { + echo "Received: $msg\n"; + }); +}; + +Worker::runAll(); +``` + +### STOMP of ReactPHP +``` +composer require react/stomp +``` + +```php +onWorkerStart = function() { + global $client; + $loop = Worker::getEventLoop(); + $factory = new React\Stomp\Factory($loop); + $client = $factory->createClient(array('vhost' => '/', 'login' => 'guest', 'passcode' => 'guest')); + + $client + ->connect() + ->then(function ($client) use ($loop) { + $client->subscribe('/topic/foo', function ($frame) { + echo "Message received: {$frame->body}\n"; + }); + }); +}; + +Worker::runAll(); +``` + + + +## Available commands +```php start.php start ``` +```php start.php start -d ``` +![workerman start](http://www.workerman.net/img/workerman-start.png) +```php start.php status ``` +![workerman satus](http://www.workerman.net/img/workerman-status.png?a=123) +```php start.php connections``` +```php start.php stop ``` +```php start.php restart ``` +```php start.php reload ``` + +## Documentation + +中文主页:[http://www.workerman.net](http://www.workerman.net) + +中文文档: [http://doc.workerman.net](http://doc.workerman.net) + +Documentation:[https://github.com/walkor/workerman-manual](https://github.com/walkor/workerman-manual/blob/master/english/src/SUMMARY.md) + +# Benchmarks +``` +CPU: Intel(R) Core(TM) i3-3220 CPU @ 3.30GHz and 4 processors totally +Memory: 8G +OS: Ubuntu 14.04 LTS +Software: ab +PHP: 5.5.9 +``` + +**Codes** +```php +count=3; +$worker->onMessage = function($connection, $data) +{ + $connection->send("HTTP/1.1 200 OK\r\nConnection: keep-alive\r\nServer: workerman\r\nContent-Length: 5\r\n\r\nhello"); +}; +Worker::runAll(); +``` +**Result** + +```shell +ab -n1000000 -c100 -k http://127.0.0.1:1234/ +This is ApacheBench, Version 2.3 <$Revision: 1528965 $> +Copyright 1996 Adam Twiss, Zeus Technology Ltd, http://www.zeustech.net/ +Licensed to The Apache Software Foundation, http://www.apache.org/ + +Benchmarking 127.0.0.1 (be patient) +Completed 100000 requests +Completed 200000 requests +Completed 300000 requests +Completed 400000 requests +Completed 500000 requests +Completed 600000 requests +Completed 700000 requests +Completed 800000 requests +Completed 900000 requests +Completed 1000000 requests +Finished 1000000 requests + + +Server Software: workerman/3.1.4 +Server Hostname: 127.0.0.1 +Server Port: 1234 + +Document Path: / +Document Length: 5 bytes + +Concurrency Level: 100 +Time taken for tests: 7.240 seconds +Complete requests: 1000000 +Failed requests: 0 +Keep-Alive requests: 1000000 +Total transferred: 73000000 bytes +HTML transferred: 5000000 bytes +Requests per second: 138124.14 [#/sec] (mean) +Time per request: 0.724 [ms] (mean) +Time per request: 0.007 [ms] (mean, across all concurrent requests) +Transfer rate: 9846.74 [Kbytes/sec] received + +Connection Times (ms) + min mean[+/-sd] median max +Connect: 0 0 0.0 0 5 +Processing: 0 1 0.2 1 9 +Waiting: 0 1 0.2 1 9 +Total: 0 1 0.2 1 9 + +Percentage of the requests served within a certain time (ms) + 50% 1 + 66% 1 + 75% 1 + 80% 1 + 90% 1 + 95% 1 + 98% 1 + 99% 1 + 100% 9 (longest request) + +``` + + +## Other links with workerman + +[PHPSocket.IO](https://github.com/walkor/phpsocket.io) +[php-socks5](https://github.com/walkor/php-socks5) +[php-http-proxy](https://github.com/walkor/php-http-proxy) + +## Donate + + +## LICENSE + +Workerman is released under the [MIT license](https://github.com/walkor/workerman/blob/master/MIT-LICENSE.txt). diff --git a/bot/workerman/WebServer.php b/bot/workerman/WebServer.php new file mode 100755 index 0000000..f529399 --- /dev/null +++ b/bot/workerman/WebServer.php @@ -0,0 +1,327 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; + +use Workerman\Protocols\Http; +use Workerman\Protocols\HttpCache; + +/** + * WebServer. + */ +class WebServer extends Worker +{ + /** + * Virtual host to path mapping. + * + * @var array ['workerman.net'=>'/home', 'www.workerman.net'=>'home/www'] + */ + protected $serverRoot = array(); + + /** + * Mime mapping. + * + * @var array + */ + protected static $mimeTypeMap = array(); + + + /** + * Used to save user OnWorkerStart callback settings. + * + * @var callback + */ + protected $_onWorkerStart = null; + + /** + * Add virtual host. + * + * @param string $domain + * @param string $config + * @return void + */ + public function addRoot($domain, $config) + { + if (is_string($config)) { + $config = array('root' => $config); + } + $this->serverRoot[$domain] = $config; + } + + /** + * Construct. + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name, $context_option = array()) + { + list(, $address) = explode(':', $socket_name, 2); + parent::__construct('http:' . $address, $context_option); + $this->name = 'WebServer'; + } + + /** + * Run webserver instance. + * + * @see Workerman.Worker::run() + */ + public function run() + { + $this->_onWorkerStart = $this->onWorkerStart; + $this->onWorkerStart = array($this, 'onWorkerStart'); + $this->onMessage = array($this, 'onMessage'); + parent::run(); + } + + /** + * Emit when process start. + * + * @throws \Exception + */ + public function onWorkerStart() + { + if (empty($this->serverRoot)) { + Worker::safeEcho(new \Exception('server root not set, please use WebServer::addRoot($domain, $root_path) to set server root path')); + exit(250); + } + + // Init mimeMap. + $this->initMimeTypeMap(); + + // Try to emit onWorkerStart callback. + if ($this->_onWorkerStart) { + try { + call_user_func($this->_onWorkerStart, $this); + } catch (\Exception $e) { + self::log($e); + exit(250); + } catch (\Error $e) { + self::log($e); + exit(250); + } + } + } + + /** + * Init mime map. + * + * @return void + */ + public function initMimeTypeMap() + { + $mime_file = Http::getMimeTypesFile(); + if (!is_file($mime_file)) { + $this->log("$mime_file mime.type file not fond"); + return; + } + $items = file($mime_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); + if (!is_array($items)) { + $this->log("get $mime_file mime.type content fail"); + return; + } + foreach ($items as $content) { + if (preg_match("/\s*(\S+)\s+(\S.+)/", $content, $match)) { + $mime_type = $match[1]; + $workerman_file_extension_var = $match[2]; + $workerman_file_extension_array = explode(' ', substr($workerman_file_extension_var, 0, -1)); + foreach ($workerman_file_extension_array as $workerman_file_extension) { + self::$mimeTypeMap[$workerman_file_extension] = $mime_type; + } + } + } + } + + /** + * Emit when http message coming. + * + * @param Connection\TcpConnection $connection + * @return void + */ + public function onMessage($connection) + { + // REQUEST_URI. + $workerman_url_info = parse_url('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']); + if (!$workerman_url_info) { + Http::header('HTTP/1.1 400 Bad Request'); + if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { + $connection->send('

400 Bad Request

'); + } else { + $connection->close('

400 Bad Request

'); + } + return; + } + + $workerman_path = isset($workerman_url_info['path']) ? $workerman_url_info['path'] : '/'; + + $workerman_path_info = pathinfo($workerman_path); + $workerman_file_extension = isset($workerman_path_info['extension']) ? $workerman_path_info['extension'] : ''; + if ($workerman_file_extension === '') { + $workerman_path = ($len = strlen($workerman_path)) && $workerman_path[$len - 1] === '/' ? $workerman_path . 'index.php' : $workerman_path . '/index.php'; + $workerman_file_extension = 'php'; + } + + $workerman_siteConfig = isset($this->serverRoot[$_SERVER['SERVER_NAME']]) ? $this->serverRoot[$_SERVER['SERVER_NAME']] : current($this->serverRoot); + $workerman_root_dir = $workerman_siteConfig['root']; + $workerman_file = "$workerman_root_dir/$workerman_path"; + if(isset($workerman_siteConfig['additionHeader'])){ + Http::header($workerman_siteConfig['additionHeader']); + } + if ($workerman_file_extension === 'php' && !is_file($workerman_file)) { + $workerman_file = "$workerman_root_dir/index.php"; + if (!is_file($workerman_file)) { + $workerman_file = "$workerman_root_dir/index.html"; + $workerman_file_extension = 'html'; + } + } + + // File exsits. + if (is_file($workerman_file)) { + // Security check. + if ((!($workerman_request_realpath = realpath($workerman_file)) || !($workerman_root_dir_realpath = realpath($workerman_root_dir))) || 0 !== strpos($workerman_request_realpath, + $workerman_root_dir_realpath) + ) { + Http::header('HTTP/1.1 400 Bad Request'); + if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { + $connection->send('

400 Bad Request

'); + } else { + $connection->close('

400 Bad Request

'); + } + return; + } + + $workerman_file = realpath($workerman_file); + + // Request php file. + if ($workerman_file_extension === 'php') { + $workerman_cwd = getcwd(); + chdir($workerman_root_dir); + ini_set('display_errors', 'off'); + ob_start(); + // Try to include php file. + try { + // $_SERVER. + $_SERVER['REMOTE_ADDR'] = $connection->getRemoteIp(); + $_SERVER['REMOTE_PORT'] = $connection->getRemotePort(); + include $workerman_file; + } catch (\Exception $e) { + // Jump_exit? + if ($e->getMessage() != 'jump_exit') { + Worker::safeEcho($e); + } + } + $content = ob_get_clean(); + ini_set('display_errors', 'on'); + if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { + $connection->send($content); + } else { + $connection->close($content); + } + chdir($workerman_cwd); + return; + } + + // Send file to client. + return self::sendFile($connection, $workerman_file); + } else { + // 404 + Http::header("HTTP/1.1 404 Not Found"); + if(isset($workerman_siteConfig['custom404']) && file_exists($workerman_siteConfig['custom404'])){ + $html404 = file_get_contents($workerman_siteConfig['custom404']); + }else{ + $html404 = '404 File not found

404 Not Found

'; + } + if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { + $connection->send($html404); + } else { + $connection->close($html404); + } + return; + } + } + + public static function sendFile($connection, $file_path) + { + // Check 304. + $info = stat($file_path); + $modified_time = $info ? date('D, d M Y H:i:s', $info['mtime']) . ' ' . date_default_timezone_get() : ''; + if (!empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) && $info) { + // Http 304. + if ($modified_time === $_SERVER['HTTP_IF_MODIFIED_SINCE']) { + // 304 + Http::header('HTTP/1.1 304 Not Modified'); + // Send nothing but http headers.. + if (strtolower($_SERVER['HTTP_CONNECTION']) === "keep-alive") { + $connection->send(''); + } else { + $connection->close(''); + } + return; + } + } + + // Http header. + if ($modified_time) { + $modified_time = "Last-Modified: $modified_time\r\n"; + } + $file_size = filesize($file_path); + $file_info = pathinfo($file_path); + $extension = isset($file_info['extension']) ? $file_info['extension'] : ''; + $file_name = isset($file_info['filename']) ? $file_info['filename'] : ''; + $header = "HTTP/1.1 200 OK\r\n"; + if (isset(self::$mimeTypeMap[$extension])) { + $header .= "Content-Type: " . self::$mimeTypeMap[$extension] . "\r\n"; + } else { + $header .= "Content-Type: application/octet-stream\r\n"; + $header .= "Content-Disposition: attachment; filename=\"$file_name\"\r\n"; + } + $header .= "Connection: keep-alive\r\n"; + $header .= $modified_time; + $header .= "Content-Length: $file_size\r\n\r\n"; + $trunk_limit_size = 1024*1024; + if ($file_size < $trunk_limit_size) { + return $connection->send($header.file_get_contents($file_path), true); + } + $connection->send($header, true); + + // Read file content from disk piece by piece and send to client. + $connection->fileHandler = fopen($file_path, 'r'); + $do_write = function()use($connection) + { + // Send buffer not full. + while(empty($connection->bufferFull)) + { + // Read from disk. + $buffer = fread($connection->fileHandler, 8192); + // Read eof. + if($buffer === '' || $buffer === false) + { + return; + } + $connection->send($buffer, true); + } + }; + // Send buffer full. + $connection->onBufferFull = function($connection) + { + $connection->bufferFull = true; + }; + // Send buffer drain. + $connection->onBufferDrain = function($connection)use($do_write) + { + $connection->bufferFull = false; + $do_write(); + }; + $do_write(); + } +} diff --git a/bot/workerman/Worker.php b/bot/workerman/Worker.php new file mode 100755 index 0000000..eda9a68 --- /dev/null +++ b/bot/workerman/Worker.php @@ -0,0 +1,2492 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ +namespace Workerman; +require_once __DIR__ . '/Lib/Constants.php'; + +use Workerman\Events\EventInterface; +use Workerman\Connection\ConnectionInterface; +use Workerman\Connection\TcpConnection; +use Workerman\Connection\UdpConnection; +use Workerman\Lib\Timer; +use Workerman\Events\Select; +use Exception; + +/** + * Worker class + * A container for listening ports + */ +class Worker +{ + /** + * Version. + * + * @var string + */ + const VERSION = '3.5.20'; + + /** + * Status starting. + * + * @var int + */ + const STATUS_STARTING = 1; + + /** + * Status running. + * + * @var int + */ + const STATUS_RUNNING = 2; + + /** + * Status shutdown. + * + * @var int + */ + const STATUS_SHUTDOWN = 4; + + /** + * Status reloading. + * + * @var int + */ + const STATUS_RELOADING = 8; + + /** + * After sending the restart command to the child process KILL_WORKER_TIMER_TIME seconds, + * if the process is still living then forced to kill. + * + * @var int + */ + const KILL_WORKER_TIMER_TIME = 2; + + /** + * Default backlog. Backlog is the maximum length of the queue of pending connections. + * + * @var int + */ + const DEFAULT_BACKLOG = 102400; + /** + * Max udp package size. + * + * @var int + */ + const MAX_UDP_PACKAGE_SIZE = 65535; + + /** + * The safe distance for columns adjacent + * + * @var int + */ + const UI_SAFE_LENGTH = 4; + + /** + * Worker id. + * + * @var int + */ + public $id = 0; + + /** + * Name of the worker processes. + * + * @var string + */ + public $name = 'none'; + + /** + * Number of worker processes. + * + * @var int + */ + public $count = 1; + + /** + * Unix user of processes, needs appropriate privileges (usually root). + * + * @var string + */ + public $user = ''; + + /** + * Unix group of processes, needs appropriate privileges (usually root). + * + * @var string + */ + public $group = ''; + + /** + * reloadable. + * + * @var bool + */ + public $reloadable = true; + + /** + * reuse port. + * + * @var bool + */ + public $reusePort = false; + + /** + * Emitted when worker processes start. + * + * @var callback + */ + public $onWorkerStart = null; + + /** + * Emitted when a socket connection is successfully established. + * + * @var callback + */ + public $onConnect = null; + + /** + * Emitted when data is received. + * + * @var callback + */ + public $onMessage = null; + + /** + * Emitted when the other end of the socket sends a FIN packet. + * + * @var callback + */ + public $onClose = null; + + /** + * Emitted when an error occurs with connection. + * + * @var callback + */ + public $onError = null; + + /** + * Emitted when the send buffer becomes full. + * + * @var callback + */ + public $onBufferFull = null; + + /** + * Emitted when the send buffer becomes empty. + * + * @var callback + */ + public $onBufferDrain = null; + + /** + * Emitted when worker processes stoped. + * + * @var callback + */ + public $onWorkerStop = null; + + /** + * Emitted when worker processes get reload signal. + * + * @var callback + */ + public $onWorkerReload = null; + + /** + * Transport layer protocol. + * + * @var string + */ + public $transport = 'tcp'; + + /** + * Store all connections of clients. + * + * @var array + */ + public $connections = array(); + + /** + * Application layer protocol. + * + * @var string + */ + public $protocol = null; + + /** + * Root path for autoload. + * + * @var string + */ + protected $_autoloadRootPath = ''; + + /** + * Pause accept new connections or not. + * + * @var bool + */ + protected $_pauseAccept = true; + + /** + * Is worker stopping ? + * @var bool + */ + public $stopping = false; + + /** + * Daemonize. + * + * @var bool + */ + public static $daemonize = false; + + /** + * Stdout file. + * + * @var string + */ + public static $stdoutFile = '/dev/null'; + + /** + * The file to store master process PID. + * + * @var string + */ + public static $pidFile = ''; + + /** + * Log file. + * + * @var mixed + */ + public static $logFile = ''; + + /** + * Global event loop. + * + * @var Events\EventInterface + */ + public static $globalEvent = null; + + /** + * Emitted when the master process get reload signal. + * + * @var callback + */ + public static $onMasterReload = null; + + /** + * Emitted when the master process terminated. + * + * @var callback + */ + public static $onMasterStop = null; + + /** + * EventLoopClass + * + * @var string + */ + public static $eventLoopClass = ''; + + /** + * The PID of master process. + * + * @var int + */ + protected static $_masterPid = 0; + + /** + * Listening socket. + * + * @var resource + */ + protected $_mainSocket = null; + + /** + * Socket name. The format is like this http://0.0.0.0:80 . + * + * @var string + */ + protected $_socketName = ''; + + /** + * Context of socket. + * + * @var resource + */ + protected $_context = null; + + /** + * All worker instances. + * + * @var Worker[] + */ + protected static $_workers = array(); + + /** + * All worker processes pid. + * The format is like this [worker_id=>[pid=>pid, pid=>pid, ..], ..] + * + * @var array + */ + protected static $_pidMap = array(); + + /** + * All worker processes waiting for restart. + * The format is like this [pid=>pid, pid=>pid]. + * + * @var array + */ + protected static $_pidsToRestart = array(); + + /** + * Mapping from PID to worker process ID. + * The format is like this [worker_id=>[0=>$pid, 1=>$pid, ..], ..]. + * + * @var array + */ + protected static $_idMap = array(); + + /** + * Current status. + * + * @var int + */ + protected static $_status = self::STATUS_STARTING; + + /** + * Maximum length of the worker names. + * + * @var int + */ + protected static $_maxWorkerNameLength = 12; + + /** + * Maximum length of the socket names. + * + * @var int + */ + protected static $_maxSocketNameLength = 12; + + /** + * Maximum length of the process user names. + * + * @var int + */ + protected static $_maxUserNameLength = 12; + + /** + * Maximum length of the Proto names. + * + * @var int + */ + protected static $_maxProtoNameLength = 4; + + /** + * Maximum length of the Processes names. + * + * @var int + */ + protected static $_maxProcessesNameLength = 9; + + /** + * Maximum length of the Status names. + * + * @var int + */ + protected static $_maxStatusNameLength = 1; + + /** + * The file to store status info of current worker process. + * + * @var string + */ + protected static $_statisticsFile = ''; + + /** + * Start file. + * + * @var string + */ + protected static $_startFile = ''; + + /** + * OS. + * + * @var string + */ + protected static $_OS = OS_TYPE_LINUX; + + /** + * Processes for windows. + * + * @var array + */ + protected static $_processForWindows = array(); + + /** + * Status info of current worker process. + * + * @var array + */ + protected static $_globalStatistics = array( + 'start_timestamp' => 0, + 'worker_exit_info' => array() + ); + + /** + * Available event loops. + * + * @var array + */ + protected static $_availableEventLoops = array( + 'libevent' => '\Workerman\Events\Libevent', + 'event' => '\Workerman\Events\Event' + // Temporarily removed swoole because it is not stable enough + //'swoole' => '\Workerman\Events\Swoole' + ); + + /** + * PHP built-in protocols. + * + * @var array + */ + protected static $_builtinTransports = array( + 'tcp' => 'tcp', + 'udp' => 'udp', + 'unix' => 'unix', + 'ssl' => 'tcp' + ); + + /** + * Graceful stop or not. + * + * @var string + */ + protected static $_gracefulStop = false; + + /** + * Standard output stream + * @var resource + */ + protected static $_outputStream = null; + + /** + * If $outputStream support decorated + * @var bool + */ + protected static $_outputDecorated = null; + + /** + * Run all worker instances. + * + * @return void + */ + public static function runAll() + { + static::checkSapiEnv(); + static::init(); + static::lock(); + static::parseCommand(); + static::daemonize(); + static::initWorkers(); + static::installSignal(); + static::saveMasterPid(); + static::unlock(); + static::displayUI(); + static::forkWorkers(); + static::resetStd(); + static::monitorWorkers(); + } + + /** + * Check sapi. + * + * @return void + */ + protected static function checkSapiEnv() + { + // Only for cli. + if (php_sapi_name() != "cli") { + exit("only run in command line mode \n"); + } + if (DIRECTORY_SEPARATOR === '\\') { + self::$_OS = OS_TYPE_WINDOWS; + } + } + + /** + * Init. + * + * @return void + */ + protected static function init() + { + set_error_handler(function($code, $msg, $file, $line){ + Worker::safeEcho("$msg in file $file on line $line\n"); + }); + + // Start file. + $backtrace = debug_backtrace(); + static::$_startFile = $backtrace[count($backtrace) - 1]['file']; + + + $unique_prefix = str_replace('/', '_', static::$_startFile); + + // Pid file. + if (empty(static::$pidFile)) { + static::$pidFile = __DIR__ . "/../$unique_prefix.pid"; + } + + // Log file. + if (empty(static::$logFile)) { + static::$logFile = __DIR__ . '/../workerman.log'; + } + $log_file = (string)static::$logFile; + if (!is_file($log_file)) { + touch($log_file); + chmod($log_file, 0622); + } + + // State. + static::$_status = static::STATUS_STARTING; + + // For statistics. + static::$_globalStatistics['start_timestamp'] = time(); + static::$_statisticsFile = sys_get_temp_dir() . "/$unique_prefix.status"; + + // Process title. + static::setProcessTitle('WorkerMan: master process start_file=' . static::$_startFile); + + // Init data for worker id. + static::initId(); + + // Timer init. + Timer::init(); + } + + /** + * Lock. + * + * @return void + */ + protected static function lock() + { + $fd = fopen(static::$_startFile, 'r'); + if (!$fd || !flock($fd, LOCK_EX)) { + static::log("Workerman[".static::$_startFile."] already running"); + exit; + } + } + + /** + * Unlock. + * + * @return void + */ + protected static function unlock() + { + $fd = fopen(static::$_startFile, 'r'); + $fd && flock($fd, LOCK_UN); + } + + /** + * Init All worker instances. + * + * @return void + */ + protected static function initWorkers() + { + if (static::$_OS !== OS_TYPE_LINUX) { + return; + } + foreach (static::$_workers as $worker) { + // Worker name. + if (empty($worker->name)) { + $worker->name = 'none'; + } + + // Get unix user of the worker process. + if (empty($worker->user)) { + $worker->user = static::getCurrentUser(); + } else { + if (posix_getuid() !== 0 && $worker->user != static::getCurrentUser()) { + static::log('Warning: You must have the root privileges to change uid and gid.'); + } + } + + // Socket name. + $worker->socket = $worker->getSocketName(); + + // Status name. + $worker->status = ' [OK] '; + + // Get column mapping for UI + foreach(static::getUiColumns() as $column_name => $prop){ + !isset($worker->{$prop}) && $worker->{$prop}= 'NNNN'; + $prop_length = strlen($worker->{$prop}); + $key = '_max' . ucfirst(strtolower($column_name)) . 'NameLength'; + static::$$key = max(static::$$key, $prop_length); + } + + // Listen. + if (!$worker->reusePort) { + $worker->listen(); + } + } + } + + /** + * Get all worker instances. + * + * @return array + */ + public static function getAllWorkers() + { + return static::$_workers; + } + + /** + * Get global event-loop instance. + * + * @return EventInterface + */ + public static function getEventLoop() + { + return static::$globalEvent; + } + + /** + * Get main socket resource + * @return resource + */ + public function getMainSocket(){ + return $this->_mainSocket; + } + + /** + * Init idMap. + * return void + */ + protected static function initId() + { + foreach (static::$_workers as $worker_id => $worker) { + $new_id_map = array(); + $worker->count = $worker->count <= 0 ? 1 : $worker->count; + for($key = 0; $key < $worker->count; $key++) { + $new_id_map[$key] = isset(static::$_idMap[$worker_id][$key]) ? static::$_idMap[$worker_id][$key] : 0; + } + static::$_idMap[$worker_id] = $new_id_map; + } + } + + /** + * Get unix user of current porcess. + * + * @return string + */ + protected static function getCurrentUser() + { + $user_info = posix_getpwuid(posix_getuid()); + return $user_info['name']; + } + + /** + * Display staring UI. + * + * @return void + */ + protected static function displayUI() + { + global $argv; + if (in_array('-q', $argv)) { + return; + } + if (static::$_OS !== OS_TYPE_LINUX) { + static::safeEcho("----------------------- WORKERMAN -----------------------------\r\n"); + static::safeEcho('Workerman version:'. static::VERSION. " PHP version:". PHP_VERSION. "\r\n"); + static::safeEcho("------------------------ WORKERS -------------------------------\r\n"); + static::safeEcho("worker listen processes status\r\n"); + return; + } + + //show version + $line_version = 'Workerman version:' . static::VERSION . str_pad('PHP version:', 22, ' ', STR_PAD_LEFT) . PHP_VERSION . PHP_EOL; + !defined('LINE_VERSIOIN_LENGTH') && define('LINE_VERSIOIN_LENGTH', strlen($line_version)); + $total_length = static::getSingleLineTotalLength(); + $line_one = '' . str_pad(' WORKERMAN ', $total_length + strlen(''), '-', STR_PAD_BOTH) . ''. PHP_EOL; + $line_two = str_pad(' WORKERS ' , $total_length + strlen(''), '-', STR_PAD_BOTH) . PHP_EOL; + static::safeEcho($line_one . $line_version . $line_two); + + //Show title + $title = ''; + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . ucfirst(strtolower($column_name)) . 'NameLength'; + //just keep compatible with listen name + $column_name == 'socket' && $column_name = 'listen'; + $title.= "{$column_name}" . str_pad('', static::$$key + static::UI_SAFE_LENGTH - strlen($column_name)); + } + $title && static::safeEcho($title . PHP_EOL); + + //Show content + foreach (static::$_workers as $worker) { + $content = ''; + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . ucfirst(strtolower($column_name)) . 'NameLength'; + preg_match_all("/(|<\/n>||<\/w>||<\/g>)/is", $worker->{$prop}, $matches); + $place_holder_length = !empty($matches) ? strlen(implode('', $matches[0])) : 0; + $content .= str_pad($worker->{$prop}, static::$$key + static::UI_SAFE_LENGTH + $place_holder_length); + } + $content && static::safeEcho($content . PHP_EOL); + } + + //Show last line + $line_last = str_pad('', static::getSingleLineTotalLength(), '-') . PHP_EOL; + !empty($content) && static::safeEcho($line_last); + + if (static::$daemonize) { + static::safeEcho("Input \"php $argv[0] stop\" to stop. Start success.\n\n"); + } else { + static::safeEcho("Press Ctrl+C to stop. Start success.\n"); + } + } + + /** + * Get UI columns to be shown in terminal + * + * 1. $column_map: array('ui_column_name' => 'clas_property_name') + * 2. Consider move into configuration in future + * + * @return array + */ + public static function getUiColumns() + { + $column_map = array( + 'proto' => 'transport', + 'user' => 'user', + 'worker' => 'name', + 'socket' => 'socket', + 'processes' => 'count', + 'status' => 'status', + ); + + return $column_map; + } + + /** + * Get single line total length for ui + * + * @return int + */ + public static function getSingleLineTotalLength() + { + $total_length = 0; + + foreach(static::getUiColumns() as $column_name => $prop){ + $key = '_max' . ucfirst(strtolower($column_name)) . 'NameLength'; + $total_length += static::$$key + static::UI_SAFE_LENGTH; + } + + //keep beauty when show less colums + !defined('LINE_VERSIOIN_LENGTH') && define('LINE_VERSIOIN_LENGTH', 0); + $total_length <= LINE_VERSIOIN_LENGTH && $total_length = LINE_VERSIOIN_LENGTH; + + return $total_length; + } + + /** + * Parse command. + * + * @return void + */ + protected static function parseCommand() + { + if (static::$_OS !== OS_TYPE_LINUX) { + return; + } + global $argv; + // Check argv; + $start_file = $argv[0]; + $available_commands = array( + 'start', + 'stop', + 'restart', + 'reload', + 'status', + 'connections', + ); + $usage = "Usage: php yourfile [mode]\nCommands: \nstart\t\tStart worker in DEBUG mode.\n\t\tUse mode -d to start in DAEMON mode.\nstop\t\tStop worker.\n\t\tUse mode -g to stop gracefully.\nrestart\t\tRestart workers.\n\t\tUse mode -d to start in DAEMON mode.\n\t\tUse mode -g to stop gracefully.\nreload\t\tReload codes.\n\t\tUse mode -g to reload gracefully.\nstatus\t\tGet worker status.\n\t\tUse mode -d to show live status.\nconnections\tGet worker connections.\n"; + if (!isset($argv[1]) || !in_array($argv[1], $available_commands)) { + if (isset($argv[1])) { + static::safeEcho('Unknown command: ' . $argv[1] . "\n"); + } + exit($usage); + } + + // Get command. + $command = trim($argv[1]); + $command2 = isset($argv[2]) ? $argv[2] : ''; + + // Start command. + $mode = ''; + if ($command === 'start') { + if ($command2 === '-d' || static::$daemonize) { + $mode = 'in DAEMON mode'; + } else { + $mode = 'in DEBUG mode'; + } + } + static::log("Workerman[$start_file] $command $mode"); + + // Get master process PID. + $master_pid = is_file(static::$pidFile) ? file_get_contents(static::$pidFile) : 0; + $master_is_alive = $master_pid && posix_kill($master_pid, 0) && posix_getpid() != $master_pid; + // Master is still alive? + if ($master_is_alive) { + if ($command === 'start') { + static::log("Workerman[$start_file] already running"); + exit; + } + } elseif ($command !== 'start' && $command !== 'restart') { + static::log("Workerman[$start_file] not run"); + exit; + } + + // execute command. + switch ($command) { + case 'start': + if ($command2 === '-d') { + static::$daemonize = true; + } + break; + case 'status': + while (1) { + if (is_file(static::$_statisticsFile)) { + @unlink(static::$_statisticsFile); + } + // Master process will send SIGUSR2 signal to all child processes. + posix_kill($master_pid, SIGUSR2); + // Sleep 1 second. + sleep(1); + // Clear terminal. + if ($command2 === '-d') { + static::safeEcho("\33[H\33[2J\33(B\33[m", true); + } + // Echo status data. + static::safeEcho(static::formatStatusData()); + if ($command2 !== '-d') { + exit(0); + } + static::safeEcho("\nPress Ctrl+C to quit.\n\n"); + } + exit(0); + case 'connections': + if (is_file(static::$_statisticsFile) && is_writable(static::$_statisticsFile)) { + unlink(static::$_statisticsFile); + } + // Master process will send SIGIO signal to all child processes. + posix_kill($master_pid, SIGIO); + // Waiting amoment. + usleep(500000); + // Display statisitcs data from a disk file. + if(is_readable(static::$_statisticsFile)) { + readfile(static::$_statisticsFile); + } + exit(0); + case 'restart': + case 'stop': + if ($command2 === '-g') { + static::$_gracefulStop = true; + $sig = SIGTERM; + static::log("Workerman[$start_file] is gracefully stopping ..."); + } else { + static::$_gracefulStop = false; + $sig = SIGINT; + static::log("Workerman[$start_file] is stopping ..."); + } + // Send stop signal to master process. + $master_pid && posix_kill($master_pid, $sig); + // Timeout. + $timeout = 5; + $start_time = time(); + // Check master process is still alive? + while (1) { + $master_is_alive = $master_pid && posix_kill($master_pid, 0); + if ($master_is_alive) { + // Timeout? + if (!static::$_gracefulStop && time() - $start_time >= $timeout) { + static::log("Workerman[$start_file] stop fail"); + exit; + } + // Waiting amoment. + usleep(10000); + continue; + } + // Stop success. + static::log("Workerman[$start_file] stop success"); + if ($command === 'stop') { + exit(0); + } + if ($command2 === '-d') { + static::$daemonize = true; + } + break; + } + break; + case 'reload': + if($command2 === '-g'){ + $sig = SIGQUIT; + }else{ + $sig = SIGUSR1; + } + posix_kill($master_pid, $sig); + exit; + default : + if (isset($command)) { + static::safeEcho('Unknown command: ' . $command . "\n"); + } + exit($usage); + } + } + + /** + * Format status data. + * + * @return string + */ + protected static function formatStatusData() + { + static $total_request_cache = array(); + if (!is_readable(static::$_statisticsFile)) { + return ''; + } + $info = file(static::$_statisticsFile, FILE_IGNORE_NEW_LINES); + if (!$info) { + return ''; + } + $status_str = ''; + $current_total_request = array(); + $worker_info = json_decode($info[0], true); + ksort($worker_info, SORT_NUMERIC); + unset($info[0]); + $data_waiting_sort = array(); + $read_process_status = false; + $total_requests = 0; + $total_qps = 0; + $total_connections = 0; + $total_fails = 0; + $total_memory = 0; + $total_timers = 0; + $maxLen1 = static::$_maxSocketNameLength; + $maxLen2 = static::$_maxWorkerNameLength; + foreach($info as $key => $value) { + if (!$read_process_status) { + $status_str .= $value . "\n"; + if (preg_match('/^pid.*?memory.*?listening/', $value)) { + $read_process_status = true; + } + continue; + } + if(preg_match('/^[0-9]+/', $value, $pid_math)) { + $pid = $pid_math[0]; + $data_waiting_sort[$pid] = $value; + if(preg_match('/^\S+?\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?(\S+?)\s+?/', $value, $match)) { + $total_memory += intval(str_ireplace('M','',$match[1])); + $maxLen1 = max($maxLen1,strlen($match[2])); + $maxLen2 = max($maxLen2,strlen($match[3])); + $total_connections += intval($match[4]); + $total_fails += intval($match[5]); + $total_timers += intval($match[6]); + $current_total_request[$pid] = $match[7]; + $total_requests += intval($match[7]); + } + } + } + foreach($worker_info as $pid => $info) { + if (!isset($data_waiting_sort[$pid])) { + $status_str .= "$pid\t" . str_pad('N/A', 7) . " " + . str_pad($info['listen'], static::$_maxSocketNameLength) . " " + . str_pad($info['name'], static::$_maxWorkerNameLength) . " " + . str_pad('N/A', 11) . " " . str_pad('N/A', 9) . " " + . str_pad('N/A', 7) . " " . str_pad('N/A', 13) . " N/A [busy] \n"; + continue; + } + //$qps = isset($total_request_cache[$pid]) ? $current_total_request[$pid] + if (!isset($total_request_cache[$pid]) || !isset($current_total_request[$pid])) { + $qps = 0; + } else { + $qps = $current_total_request[$pid] - $total_request_cache[$pid]; + $total_qps += $qps; + } + $status_str .= $data_waiting_sort[$pid]. " " . str_pad($qps, 6) ." [idle]\n"; + } + $total_request_cache = $current_total_request; + $status_str .= "----------------------------------------------PROCESS STATUS---------------------------------------------------\n"; + $status_str .= "Summary\t" . str_pad($total_memory.'M', 7) . " " + . str_pad('-', $maxLen1) . " " + . str_pad('-', $maxLen2) . " " + . str_pad($total_connections, 11) . " " . str_pad($total_fails, 9) . " " + . str_pad($total_timers, 7) . " " . str_pad($total_requests, 13) . " " + . str_pad($total_qps,6)." [Summary] \n"; + return $status_str; + } + + + /** + * Install signal handler. + * + * @return void + */ + protected static function installSignal() + { + if (static::$_OS !== OS_TYPE_LINUX) { + return; + } + // stop + pcntl_signal(SIGINT, array('\Workerman\Worker', 'signalHandler'), false); + // graceful stop + pcntl_signal(SIGTERM, array('\Workerman\Worker', 'signalHandler'), false); + // reload + pcntl_signal(SIGUSR1, array('\Workerman\Worker', 'signalHandler'), false); + // graceful reload + pcntl_signal(SIGQUIT, array('\Workerman\Worker', 'signalHandler'), false); + // status + pcntl_signal(SIGUSR2, array('\Workerman\Worker', 'signalHandler'), false); + // connection status + pcntl_signal(SIGIO, array('\Workerman\Worker', 'signalHandler'), false); + // ignore + pcntl_signal(SIGPIPE, SIG_IGN, false); + } + + /** + * Reinstall signal handler. + * + * @return void + */ + protected static function reinstallSignal() + { + if (static::$_OS !== OS_TYPE_LINUX) { + return; + } + // uninstall stop signal handler + pcntl_signal(SIGINT, SIG_IGN, false); + // uninstall graceful stop signal handler + pcntl_signal(SIGTERM, SIG_IGN, false); + // uninstall reload signal handler + pcntl_signal(SIGUSR1, SIG_IGN, false); + // uninstall graceful reload signal handler + pcntl_signal(SIGQUIT, SIG_IGN, false); + // uninstall status signal handler + pcntl_signal(SIGUSR2, SIG_IGN, false); + // uninstall connections status signal handler + pcntl_signal(SIGIO, SIG_IGN, false); + // reinstall stop signal handler + static::$globalEvent->add(SIGINT, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); + // reinstall graceful stop signal handler + static::$globalEvent->add(SIGTERM, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); + // reinstall reload signal handler + static::$globalEvent->add(SIGUSR1, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); + // reinstall graceful reload signal handler + static::$globalEvent->add(SIGQUIT, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); + // reinstall status signal handler + static::$globalEvent->add(SIGUSR2, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); + // reinstall connection status signal handler + static::$globalEvent->add(SIGIO, EventInterface::EV_SIGNAL, array('\Workerman\Worker', 'signalHandler')); + } + + /** + * Signal handler. + * + * @param int $signal + */ + public static function signalHandler($signal) + { + switch ($signal) { + // Stop. + case SIGINT: + static::$_gracefulStop = false; + static::stopAll(); + break; + // Graceful stop. + case SIGTERM: + static::$_gracefulStop = true; + static::stopAll(); + break; + // Reload. + case SIGQUIT: + case SIGUSR1: + if($signal === SIGQUIT){ + static::$_gracefulStop = true; + }else{ + static::$_gracefulStop = false; + } + static::$_pidsToRestart = static::getAllWorkerPids(); + static::reload(); + break; + // Show status. + case SIGUSR2: + static::writeStatisticsToStatusFile(); + break; + // Show connection status. + case SIGIO: + static::writeConnectionsStatisticsToStatusFile(); + break; + } + } + + /** + * Run as deamon mode. + * + * @throws Exception + */ + protected static function daemonize() + { + if (!static::$daemonize || static::$_OS !== OS_TYPE_LINUX) { + return; + } + umask(0); + $pid = pcntl_fork(); + if (-1 === $pid) { + throw new Exception('fork fail'); + } elseif ($pid > 0) { + exit(0); + } + if (-1 === posix_setsid()) { + throw new Exception("setsid fail"); + } + // Fork again avoid SVR4 system regain the control of terminal. + $pid = pcntl_fork(); + if (-1 === $pid) { + throw new Exception("fork fail"); + } elseif (0 !== $pid) { + exit(0); + } + } + + /** + * Redirect standard input and output. + * + * @throws Exception + */ + public static function resetStd() + { + if (!static::$daemonize || static::$_OS !== OS_TYPE_LINUX) { + return; + } + global $STDOUT, $STDERR; + $handle = fopen(static::$stdoutFile, "a"); + if ($handle) { + unset($handle); + set_error_handler(function(){}); + fclose($STDOUT); + fclose($STDERR); + fclose(STDOUT); + fclose(STDERR); + $STDOUT = fopen(static::$stdoutFile, "a"); + $STDERR = fopen(static::$stdoutFile, "a"); + // change output stream + static::$_outputStream = null; + static::outputStream($STDOUT); + restore_error_handler(); + } else { + throw new Exception('can not open stdoutFile ' . static::$stdoutFile); + } + } + + /** + * Save pid. + * + * @throws Exception + */ + protected static function saveMasterPid() + { + if (static::$_OS !== OS_TYPE_LINUX) { + return; + } + + static::$_masterPid = posix_getpid(); + if (false === file_put_contents(static::$pidFile, static::$_masterPid)) { + throw new Exception('can not save pid to ' . static::$pidFile); + } + } + + /** + * Get event loop name. + * + * @return string + */ + protected static function getEventLoopName() + { + if (static::$eventLoopClass) { + return static::$eventLoopClass; + } + + if (!class_exists('\Swoole\Event', false)) { + unset(static::$_availableEventLoops['swoole']); + } + + $loop_name = ''; + foreach (static::$_availableEventLoops as $name=>$class) { + if (extension_loaded($name)) { + $loop_name = $name; + break; + } + } + + if ($loop_name) { + if (interface_exists('\React\EventLoop\LoopInterface')) { + switch ($loop_name) { + case 'libevent': + static::$eventLoopClass = '\Workerman\Events\React\ExtLibEventLoop'; + break; + case 'event': + static::$eventLoopClass = '\Workerman\Events\React\ExtEventLoop'; + break; + default : + static::$eventLoopClass = '\Workerman\Events\React\StreamSelectLoop'; + break; + } + } else { + static::$eventLoopClass = static::$_availableEventLoops[$loop_name]; + } + } else { + static::$eventLoopClass = interface_exists('\React\EventLoop\LoopInterface')? '\Workerman\Events\React\StreamSelectLoop':'\Workerman\Events\Select'; + } + return static::$eventLoopClass; + } + + /** + * Get all pids of worker processes. + * + * @return array + */ + protected static function getAllWorkerPids() + { + $pid_array = array(); + foreach (static::$_pidMap as $worker_pid_array) { + foreach ($worker_pid_array as $worker_pid) { + $pid_array[$worker_pid] = $worker_pid; + } + } + return $pid_array; + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkers() + { + if (static::$_OS === OS_TYPE_LINUX) { + static::forkWorkersForLinux(); + } else { + static::forkWorkersForWindows(); + } + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkersForLinux() + { + + foreach (static::$_workers as $worker) { + if (static::$_status === static::STATUS_STARTING) { + if (empty($worker->name)) { + $worker->name = $worker->getSocketName(); + } + $worker_name_length = strlen($worker->name); + if (static::$_maxWorkerNameLength < $worker_name_length) { + static::$_maxWorkerNameLength = $worker_name_length; + } + } + + while (count(static::$_pidMap[$worker->workerId]) < $worker->count) { + static::forkOneWorkerForLinux($worker); + } + } + } + + /** + * Fork some worker processes. + * + * @return void + */ + protected static function forkWorkersForWindows() + { + $files = static::getStartFilesForWindows(); + global $argv; + if(in_array('-q', $argv) || count($files) === 1) + { + if(count(static::$_workers) > 1) + { + static::safeEcho("@@@ Error: multi workers init in one php file are not support @@@\r\n"); + static::safeEcho("@@@ See http://doc.workerman.net/faq/multi-woker-for-windows.html @@@\r\n"); + } + elseif(count(static::$_workers) <= 0) + { + exit("@@@no worker inited@@@\r\n\r\n"); + } + + reset(static::$_workers); + /** @var Worker $worker */ + $worker = current(static::$_workers); + + // Display UI. + static::safeEcho(str_pad($worker->name, 21) . str_pad($worker->getSocketName(), 36) . str_pad($worker->count, 10) . "[ok]\n"); + $worker->listen(); + $worker->run(); + exit("@@@child exit@@@\r\n"); + } + else + { + static::$globalEvent = new \Workerman\Events\Select(); + Timer::init(static::$globalEvent); + foreach($files as $start_file) + { + static::forkOneWorkerForWindows($start_file); + } + } + } + + /** + * Get start files for windows. + * + * @return array + */ + public static function getStartFilesForWindows() { + global $argv; + $files = array(); + foreach($argv as $file) + { + if(is_file($file)) + { + $files[$file] = $file; + } + } + return $files; + } + + /** + * Fork one worker process. + * + * @param string $start_file + */ + public static function forkOneWorkerForWindows($start_file) + { + $start_file = realpath($start_file); + $std_file = sys_get_temp_dir() . '/'.str_replace(array('/', "\\", ':'), '_', $start_file).'.out.txt'; + + $descriptorspec = array( + 0 => array('pipe', 'a'), // stdin + 1 => array('file', $std_file, 'w'), // stdout + 2 => array('file', $std_file, 'w') // stderr + ); + + + $pipes = array(); + $process = proc_open("php \"$start_file\" -q", $descriptorspec, $pipes); + $std_handler = fopen($std_file, 'a+'); + stream_set_blocking($std_handler, 0); + + if (empty(static::$globalEvent)) { + static::$globalEvent = new Select(); + Timer::init(static::$globalEvent); + } + $timer_id = Timer::add(0.1, function()use($std_handler) + { + Worker::safeEcho(fread($std_handler, 65535)); + }); + + // 保存子进程句柄 + static::$_processForWindows[$start_file] = array($process, $start_file, $timer_id); + } + + /** + * check worker status for windows. + * @return void + */ + public static function checkWorkerStatusForWindows() + { + foreach(static::$_processForWindows as $process_data) + { + $process = $process_data[0]; + $start_file = $process_data[1]; + $timer_id = $process_data[2]; + $status = proc_get_status($process); + if(isset($status['running'])) + { + if(!$status['running']) + { + static::safeEcho("process $start_file terminated and try to restart\n"); + Timer::del($timer_id); + proc_close($process); + static::forkOneWorkerForWindows($start_file); + } + } + else + { + static::safeEcho("proc_get_status fail\n"); + } + } + } + + + /** + * Fork one worker process. + * + * @param \Workerman\Worker $worker + * @throws Exception + */ + protected static function forkOneWorkerForLinux($worker) + { + // Get available worker id. + $id = static::getId($worker->workerId, 0); + if ($id === false) { + return; + } + $pid = pcntl_fork(); + // For master process. + if ($pid > 0) { + static::$_pidMap[$worker->workerId][$pid] = $pid; + static::$_idMap[$worker->workerId][$id] = $pid; + } // For child processes. + elseif (0 === $pid) { + srand(); + mt_srand(); + if ($worker->reusePort) { + $worker->listen(); + } + if (static::$_status === static::STATUS_STARTING) { + static::resetStd(); + } + static::$_pidMap = array(); + // Remove other listener. + foreach(static::$_workers as $key => $one_worker) { + if ($one_worker->workerId !== $worker->workerId) { + $one_worker->unlisten(); + unset(static::$_workers[$key]); + } + } + Timer::delAll(); + static::setProcessTitle('WorkerMan: worker process ' . $worker->name . ' ' . $worker->getSocketName()); + $worker->setUserAndGroup(); + $worker->id = $id; + $worker->run(); + $err = new Exception('event-loop exited'); + static::log($err); + exit(250); + } else { + throw new Exception("forkOneWorker fail"); + } + } + + /** + * Get worker id. + * + * @param int $worker_id + * @param int $pid + * + * @return integer + */ + protected static function getId($worker_id, $pid) + { + return array_search($pid, static::$_idMap[$worker_id]); + } + + /** + * Set unix user and group for current process. + * + * @return void + */ + public function setUserAndGroup() + { + // Get uid. + $user_info = posix_getpwnam($this->user); + if (!$user_info) { + static::log("Warning: User {$this->user} not exsits"); + return; + } + $uid = $user_info['uid']; + // Get gid. + if ($this->group) { + $group_info = posix_getgrnam($this->group); + if (!$group_info) { + static::log("Warning: Group {$this->group} not exsits"); + return; + } + $gid = $group_info['gid']; + } else { + $gid = $user_info['gid']; + } + + // Set uid and gid. + if ($uid != posix_getuid() || $gid != posix_getgid()) { + if (!posix_setgid($gid) || !posix_initgroups($user_info['name'], $gid) || !posix_setuid($uid)) { + static::log("Warning: change gid or uid fail."); + } + } + } + + /** + * Set process name. + * + * @param string $title + * @return void + */ + protected static function setProcessTitle($title) + { + set_error_handler(function(){}); + // >=php 5.5 + if (function_exists('cli_set_process_title')) { + cli_set_process_title($title); + } // Need proctitle when php<=5.5 . + elseif (extension_loaded('proctitle') && function_exists('setproctitle')) { + setproctitle($title); + } + restore_error_handler(); + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkers() + { + if (static::$_OS === OS_TYPE_LINUX) { + static::monitorWorkersForLinux(); + } else { + static::monitorWorkersForWindows(); + } + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkersForLinux() + { + static::$_status = static::STATUS_RUNNING; + while (1) { + // Calls signal handlers for pending signals. + pcntl_signal_dispatch(); + // Suspends execution of the current process until a child has exited, or until a signal is delivered + $status = 0; + $pid = pcntl_wait($status, WUNTRACED); + // Calls signal handlers for pending signals again. + pcntl_signal_dispatch(); + // If a child has already exited. + if ($pid > 0) { + // Find out witch worker process exited. + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + if (isset($worker_pid_array[$pid])) { + $worker = static::$_workers[$worker_id]; + // Exit status. + if ($status !== 0) { + static::log("worker[" . $worker->name . ":$pid] exit with status $status"); + } + + // For Statistics. + if (!isset(static::$_globalStatistics['worker_exit_info'][$worker_id][$status])) { + static::$_globalStatistics['worker_exit_info'][$worker_id][$status] = 0; + } + static::$_globalStatistics['worker_exit_info'][$worker_id][$status]++; + + // Clear process data. + unset(static::$_pidMap[$worker_id][$pid]); + + // Mark id is available. + $id = static::getId($worker_id, $pid); + static::$_idMap[$worker_id][$id] = 0; + + break; + } + } + // Is still running state then fork a new worker process. + if (static::$_status !== static::STATUS_SHUTDOWN) { + static::forkWorkers(); + // If reloading continue. + if (isset(static::$_pidsToRestart[$pid])) { + unset(static::$_pidsToRestart[$pid]); + static::reload(); + } + } + } + + // If shutdown state and all child processes exited then master process exit. + if (static::$_status === static::STATUS_SHUTDOWN && !static::getAllWorkerPids()) { + static::exitAndClearAll(); + } + } + } + + /** + * Monitor all child processes. + * + * @return void + */ + protected static function monitorWorkersForWindows() + { + Timer::add(1, "\\Workerman\\Worker::checkWorkerStatusForWindows"); + + static::$globalEvent->loop(); + } + + /** + * Exit current process. + * + * @return void + */ + protected static function exitAndClearAll() + { + foreach (static::$_workers as $worker) { + $socket_name = $worker->getSocketName(); + if ($worker->transport === 'unix' && $socket_name) { + list(, $address) = explode(':', $socket_name, 2); + @unlink($address); + } + } + @unlink(static::$pidFile); + static::log("Workerman[" . basename(static::$_startFile) . "] has been stopped"); + if (static::$onMasterStop) { + call_user_func(static::$onMasterStop); + } + exit(0); + } + + /** + * Execute reload. + * + * @return void + */ + protected static function reload() + { + // For master process. + if (static::$_masterPid === posix_getpid()) { + // Set reloading state. + if (static::$_status !== static::STATUS_RELOADING && static::$_status !== static::STATUS_SHUTDOWN) { + static::log("Workerman[" . basename(static::$_startFile) . "] reloading"); + static::$_status = static::STATUS_RELOADING; + // Try to emit onMasterReload callback. + if (static::$onMasterReload) { + try { + call_user_func(static::$onMasterReload); + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + static::initId(); + } + } + + if (static::$_gracefulStop) { + $sig = SIGQUIT; + } else { + $sig = SIGUSR1; + } + + // Send reload signal to all child processes. + $reloadable_pid_array = array(); + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + $worker = static::$_workers[$worker_id]; + if ($worker->reloadable) { + foreach ($worker_pid_array as $pid) { + $reloadable_pid_array[$pid] = $pid; + } + } else { + foreach ($worker_pid_array as $pid) { + // Send reload signal to a worker process which reloadable is false. + posix_kill($pid, $sig); + } + } + } + + // Get all pids that are waiting reload. + static::$_pidsToRestart = array_intersect(static::$_pidsToRestart, $reloadable_pid_array); + + // Reload complete. + if (empty(static::$_pidsToRestart)) { + if (static::$_status !== static::STATUS_SHUTDOWN) { + static::$_status = static::STATUS_RUNNING; + } + return; + } + // Continue reload. + $one_worker_pid = current(static::$_pidsToRestart); + // Send reload signal to a worker process. + posix_kill($one_worker_pid, $sig); + // If the process does not exit after static::KILL_WORKER_TIMER_TIME seconds try to kill it. + if(!static::$_gracefulStop){ + Timer::add(static::KILL_WORKER_TIMER_TIME, 'posix_kill', array($one_worker_pid, SIGKILL), false); + } + } // For child processes. + else { + reset(static::$_workers); + $worker = current(static::$_workers); + // Try to emit onWorkerReload callback. + if ($worker->onWorkerReload) { + try { + call_user_func($worker->onWorkerReload, $worker); + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + } + + if ($worker->reloadable) { + static::stopAll(); + } + } + } + + /** + * Stop. + * + * @return void + */ + public static function stopAll() + { + static::$_status = static::STATUS_SHUTDOWN; + // For master process. + if (static::$_masterPid === posix_getpid()) { + static::log("Workerman[" . basename(static::$_startFile) . "] stopping ..."); + $worker_pid_array = static::getAllWorkerPids(); + // Send stop signal to all child processes. + if (static::$_gracefulStop) { + $sig = SIGTERM; + } else { + $sig = SIGINT; + } + foreach ($worker_pid_array as $worker_pid) { + posix_kill($worker_pid, $sig); + if(!static::$_gracefulStop){ + Timer::add(static::KILL_WORKER_TIMER_TIME, 'posix_kill', array($worker_pid, SIGKILL), false); + } + } + Timer::add(1, "\\Workerman\\Worker::checkIfChildRunning"); + // Remove statistics file. + if (is_file(static::$_statisticsFile)) { + @unlink(static::$_statisticsFile); + } + } // For child processes. + else { + // Execute exit. + foreach (static::$_workers as $worker) { + if(!$worker->stopping){ + $worker->stop(); + $worker->stopping = true; + } + } + if (!static::$_gracefulStop || ConnectionInterface::$statistics['connection_count'] <= 0) { + static::$_workers = array(); + if (static::$globalEvent) { + static::$globalEvent->destroy(); + } + exit(0); + } + } + } + + /** + * check if child processes is really running + */ + public static function checkIfChildRunning() + { + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + foreach ($worker_pid_array as $pid => $worker_pid) { + if (!posix_kill($pid, 0)) { + unset(static::$_pidMap[$worker_id][$pid]); + } + } + } + } + + /** + * Get process status. + * + * @return number + */ + public static function getStatus() + { + return static::$_status; + } + + /** + * If stop gracefully. + * + * @return boolean + */ + public static function getGracefulStop() + { + return static::$_gracefulStop; + } + + /** + * Write statistics data to disk. + * + * @return void + */ + protected static function writeStatisticsToStatusFile() + { + // For master process. + if (static::$_masterPid === posix_getpid()) { + $all_worker_info = array(); + foreach(static::$_pidMap as $worker_id => $pid_array) { + /** @var /Workerman/Worker $worker */ + $worker = static::$_workers[$worker_id]; + foreach($pid_array as $pid) { + $all_worker_info[$pid] = array('name' => $worker->name, 'listen' => $worker->getSocketName()); + } + } + + file_put_contents(static::$_statisticsFile, json_encode($all_worker_info)."\n", FILE_APPEND); + $loadavg = function_exists('sys_getloadavg') ? array_map('round', sys_getloadavg(), array(2)) : array('-', '-', '-'); + file_put_contents(static::$_statisticsFile, + "----------------------------------------------GLOBAL STATUS----------------------------------------------------\n", FILE_APPEND); + file_put_contents(static::$_statisticsFile, + 'Workerman version:' . static::VERSION . " PHP version:" . PHP_VERSION . "\n", FILE_APPEND); + file_put_contents(static::$_statisticsFile, 'start time:' . date('Y-m-d H:i:s', + static::$_globalStatistics['start_timestamp']) . ' run ' . floor((time() - static::$_globalStatistics['start_timestamp']) / (24 * 60 * 60)) . ' days ' . floor(((time() - static::$_globalStatistics['start_timestamp']) % (24 * 60 * 60)) / (60 * 60)) . " hours \n", + FILE_APPEND); + $load_str = 'load average: ' . implode(", ", $loadavg); + file_put_contents(static::$_statisticsFile, + str_pad($load_str, 33) . 'event-loop:' . static::getEventLoopName() . "\n", FILE_APPEND); + file_put_contents(static::$_statisticsFile, + count(static::$_pidMap) . ' workers ' . count(static::getAllWorkerPids()) . " processes\n", + FILE_APPEND); + file_put_contents(static::$_statisticsFile, + str_pad('worker_name', static::$_maxWorkerNameLength) . " exit_status exit_count\n", FILE_APPEND); + foreach (static::$_pidMap as $worker_id => $worker_pid_array) { + $worker = static::$_workers[$worker_id]; + if (isset(static::$_globalStatistics['worker_exit_info'][$worker_id])) { + foreach (static::$_globalStatistics['worker_exit_info'][$worker_id] as $worker_exit_status => $worker_exit_count) { + file_put_contents(static::$_statisticsFile, + str_pad($worker->name, static::$_maxWorkerNameLength) . " " . str_pad($worker_exit_status, + 16) . " $worker_exit_count\n", FILE_APPEND); + } + } else { + file_put_contents(static::$_statisticsFile, + str_pad($worker->name, static::$_maxWorkerNameLength) . " " . str_pad(0, 16) . " 0\n", + FILE_APPEND); + } + } + file_put_contents(static::$_statisticsFile, + "----------------------------------------------PROCESS STATUS---------------------------------------------------\n", + FILE_APPEND); + file_put_contents(static::$_statisticsFile, + "pid\tmemory " . str_pad('listening', static::$_maxSocketNameLength) . " " . str_pad('worker_name', + static::$_maxWorkerNameLength) . " connections " . str_pad('send_fail', 9) . " " + . str_pad('timers', 8) . str_pad('total_request', 13) ." qps status\n", FILE_APPEND); + + chmod(static::$_statisticsFile, 0722); + + foreach (static::getAllWorkerPids() as $worker_pid) { + posix_kill($worker_pid, SIGUSR2); + } + return; + } + + // For child processes. + reset(static::$_workers); + /** @var \Workerman\Worker $worker */ + $worker = current(static::$_workers); + $worker_status_str = posix_getpid() . "\t" . str_pad(round(memory_get_usage(true) / (1024 * 1024), 2) . "M", 7) + . " " . str_pad($worker->getSocketName(), static::$_maxSocketNameLength) . " " + . str_pad(($worker->name === $worker->getSocketName() ? 'none' : $worker->name), static::$_maxWorkerNameLength) + . " "; + $worker_status_str .= str_pad(ConnectionInterface::$statistics['connection_count'], 11) + . " " . str_pad(ConnectionInterface::$statistics['send_fail'], 9) + . " " . str_pad(static::$globalEvent->getTimerCount(), 7) + . " " . str_pad(ConnectionInterface::$statistics['total_request'], 13) . "\n"; + file_put_contents(static::$_statisticsFile, $worker_status_str, FILE_APPEND); + } + + /** + * Write statistics data to disk. + * + * @return void + */ + protected static function writeConnectionsStatisticsToStatusFile() + { + // For master process. + if (static::$_masterPid === posix_getpid()) { + file_put_contents(static::$_statisticsFile, "--------------------------------------------------------------------- WORKERMAN CONNECTION STATUS --------------------------------------------------------------------------------\n", FILE_APPEND); + file_put_contents(static::$_statisticsFile, "PID Worker CID Trans Protocol ipv4 ipv6 Recv-Q Send-Q Bytes-R Bytes-W Status Local Address Foreign Address\n", FILE_APPEND); + chmod(static::$_statisticsFile, 0722); + foreach (static::getAllWorkerPids() as $worker_pid) { + posix_kill($worker_pid, SIGIO); + } + return; + } + + // For child processes. + $bytes_format = function($bytes) + { + if($bytes > 1024*1024*1024*1024) { + return round($bytes/(1024*1024*1024*1024), 1)."TB"; + } + if($bytes > 1024*1024*1024) { + return round($bytes/(1024*1024*1024), 1)."GB"; + } + if($bytes > 1024*1024) { + return round($bytes/(1024*1024), 1)."MB"; + } + if($bytes > 1024) { + return round($bytes/(1024), 1)."KB"; + } + return $bytes."B"; + }; + + $pid = posix_getpid(); + $str = ''; + reset(static::$_workers); + $current_worker = current(static::$_workers); + $default_worker_name = $current_worker->name; + + /** @var \Workerman\Worker $worker */ + foreach(TcpConnection::$connections as $connection) { + /** @var \Workerman\Connection\TcpConnection $connection */ + $transport = $connection->transport; + $ipv4 = $connection->isIpV4() ? ' 1' : ' 0'; + $ipv6 = $connection->isIpV6() ? ' 1' : ' 0'; + $recv_q = $bytes_format($connection->getRecvBufferQueueSize()); + $send_q = $bytes_format($connection->getSendBufferQueueSize()); + $local_address = trim($connection->getLocalAddress()); + $remote_address = trim($connection->getRemoteAddress()); + $state = $connection->getStatus(false); + $bytes_read = $bytes_format($connection->bytesRead); + $bytes_written = $bytes_format($connection->bytesWritten); + $id = $connection->id; + $protocol = $connection->protocol ? $connection->protocol : $connection->transport; + $pos = strrpos($protocol, '\\'); + if ($pos) { + $protocol = substr($protocol, $pos+1); + } + if (strlen($protocol) > 15) { + $protocol = substr($protocol, 0, 13) . '..'; + } + $worker_name = isset($connection->worker) ? $connection->worker->name : $default_worker_name; + if (strlen($worker_name) > 14) { + $worker_name = substr($worker_name, 0, 12) . '..'; + } + $str .= str_pad($pid, 9) . str_pad($worker_name, 16) . str_pad($id, 10) . str_pad($transport, 8) + . str_pad($protocol, 16) . str_pad($ipv4, 7) . str_pad($ipv6, 7) . str_pad($recv_q, 13) + . str_pad($send_q, 13) . str_pad($bytes_read, 13) . str_pad($bytes_written, 13) . ' ' + . str_pad($state, 14) . ' ' . str_pad($local_address, 22) . ' ' . str_pad($remote_address, 22) ."\n"; + } + if ($str) { + file_put_contents(static::$_statisticsFile, $str, FILE_APPEND); + } + } + + /** + * Check errors when current process exited. + * + * @return void + */ + public static function checkErrors() + { + if (static::STATUS_SHUTDOWN != static::$_status) { + $error_msg = static::$_OS === OS_TYPE_LINUX ? 'Worker['. posix_getpid() .'] process terminated' : 'Worker process terminated'; + $errors = error_get_last(); + if ($errors && ($errors['type'] === E_ERROR || + $errors['type'] === E_PARSE || + $errors['type'] === E_CORE_ERROR || + $errors['type'] === E_COMPILE_ERROR || + $errors['type'] === E_RECOVERABLE_ERROR) + ) { + $error_msg .= ' with ERROR: ' . static::getErrorType($errors['type']) . " \"{$errors['message']} in {$errors['file']} on line {$errors['line']}\""; + } + static::log($error_msg); + } + } + + /** + * Get error message by error code. + * + * @param integer $type + * @return string + */ + protected static function getErrorType($type) + { + switch ($type) { + case E_ERROR: // 1 // + return 'E_ERROR'; + case E_WARNING: // 2 // + return 'E_WARNING'; + case E_PARSE: // 4 // + return 'E_PARSE'; + case E_NOTICE: // 8 // + return 'E_NOTICE'; + case E_CORE_ERROR: // 16 // + return 'E_CORE_ERROR'; + case E_CORE_WARNING: // 32 // + return 'E_CORE_WARNING'; + case E_COMPILE_ERROR: // 64 // + return 'E_COMPILE_ERROR'; + case E_COMPILE_WARNING: // 128 // + return 'E_COMPILE_WARNING'; + case E_USER_ERROR: // 256 // + return 'E_USER_ERROR'; + case E_USER_WARNING: // 512 // + return 'E_USER_WARNING'; + case E_USER_NOTICE: // 1024 // + return 'E_USER_NOTICE'; + case E_STRICT: // 2048 // + return 'E_STRICT'; + case E_RECOVERABLE_ERROR: // 4096 // + return 'E_RECOVERABLE_ERROR'; + case E_DEPRECATED: // 8192 // + return 'E_DEPRECATED'; + case E_USER_DEPRECATED: // 16384 // + return 'E_USER_DEPRECATED'; + } + return ""; + } + + /** + * Log. + * + * @param string $msg + * @return void + */ + public static function log($msg) + { + $msg = $msg . "\n"; + if (!static::$daemonize) { + static::safeEcho($msg); + } + file_put_contents((string)static::$logFile, date('Y-m-d H:i:s') . ' ' . 'pid:' + . (static::$_OS === OS_TYPE_LINUX ? posix_getpid() : 1) . ' ' . $msg, FILE_APPEND | LOCK_EX); + } + + /** + * Safe Echo. + * @param $msg + * @param bool $decorated + * @return bool + */ + public static function safeEcho($msg, $decorated = false) + { + $stream = static::outputStream(); + if (!$stream) { + return false; + } + if (!$decorated) { + $line = $white = $green = $end = ''; + if (static::$_outputDecorated) { + $line = "\033[1A\n\033[K"; + $white = "\033[47;30m"; + $green = "\033[32;40m"; + $end = "\033[0m"; + } + $msg = str_replace(array('', '', ''), array($line, $white, $green), $msg); + $msg = str_replace(array('', '', ''), $end, $msg); + } elseif (!static::$_outputDecorated) { + return false; + } + fwrite($stream, $msg); + fflush($stream); + return true; + } + + /** + * @param null $stream + * @return bool|resource + */ + private static function outputStream($stream = null) + { + if (!$stream) { + $stream = static::$_outputStream ? static::$_outputStream : STDOUT; + } + if (!$stream || !is_resource($stream) || 'stream' !== get_resource_type($stream)) { + return false; + } + $stat = fstat($stream); + if (($stat['mode'] & 0170000) === 0100000) { + // file + static::$_outputDecorated = false; + } else { + static::$_outputDecorated = + static::$_OS === OS_TYPE_LINUX && + function_exists('posix_isatty') && + posix_isatty($stream); + } + return static::$_outputStream = $stream; + } + + /** + * Construct. + * + * @param string $socket_name + * @param array $context_option + */ + public function __construct($socket_name = '', $context_option = array()) + { + // Save all worker instances. + $this->workerId = spl_object_hash($this); + static::$_workers[$this->workerId] = $this; + static::$_pidMap[$this->workerId] = array(); + + // Get autoload root path. + $backtrace = debug_backtrace(); + $this->_autoloadRootPath = dirname($backtrace[0]['file']); + + // Context for socket. + if ($socket_name) { + $this->_socketName = $socket_name; + if (!isset($context_option['socket']['backlog'])) { + $context_option['socket']['backlog'] = static::DEFAULT_BACKLOG; + } + $this->_context = stream_context_create($context_option); + } + } + + + /** + * Listen. + * + * @throws Exception + */ + public function listen() + { + if (!$this->_socketName) { + return; + } + + // Autoload. + Autoloader::setRootPath($this->_autoloadRootPath); + + if (!$this->_mainSocket) { + // Get the application layer communication protocol and listening address. + list($scheme, $address) = explode(':', $this->_socketName, 2); + // Check application layer protocol class. + if (!isset(static::$_builtinTransports[$scheme])) { + $scheme = ucfirst($scheme); + $this->protocol = substr($scheme,0,1)==='\\' ? $scheme : '\\Protocols\\' . $scheme; + if (!class_exists($this->protocol)) { + $this->protocol = "\\Workerman\\Protocols\\$scheme"; + if (!class_exists($this->protocol)) { + throw new Exception("class \\Protocols\\$scheme not exist"); + } + } + + if (!isset(static::$_builtinTransports[$this->transport])) { + throw new \Exception('Bad worker->transport ' . var_export($this->transport, true)); + } + } else { + $this->transport = $scheme; + } + + $local_socket = static::$_builtinTransports[$this->transport] . ":" . $address; + + // Flag. + $flags = $this->transport === 'udp' ? STREAM_SERVER_BIND : STREAM_SERVER_BIND | STREAM_SERVER_LISTEN; + $errno = 0; + $errmsg = ''; + // SO_REUSEPORT. + if ($this->reusePort) { + stream_context_set_option($this->_context, 'socket', 'so_reuseport', 1); + } + + // Create an Internet or Unix domain server socket. + $this->_mainSocket = stream_socket_server($local_socket, $errno, $errmsg, $flags, $this->_context); + if (!$this->_mainSocket) { + throw new Exception($errmsg); + } + + if ($this->transport === 'ssl') { + stream_socket_enable_crypto($this->_mainSocket, false); + } elseif ($this->transport === 'unix') { + $socketFile = substr($address, 2); + if ($this->user) { + chown($socketFile, $this->user); + } + if ($this->group) { + chgrp($socketFile, $this->group); + } + } + + // Try to open keepalive for tcp and disable Nagle algorithm. + if (function_exists('socket_import_stream') && static::$_builtinTransports[$this->transport] === 'tcp') { + set_error_handler(function(){}); + $socket = socket_import_stream($this->_mainSocket); + socket_set_option($socket, SOL_SOCKET, SO_KEEPALIVE, 1); + socket_set_option($socket, SOL_TCP, TCP_NODELAY, 1); + restore_error_handler(); + } + + // Non blocking. + stream_set_blocking($this->_mainSocket, 0); + } + + $this->resumeAccept(); + } + + /** + * Unlisten. + * + * @return void + */ + public function unlisten() { + $this->pauseAccept(); + if ($this->_mainSocket) { + set_error_handler(function(){}); + fclose($this->_mainSocket); + restore_error_handler(); + $this->_mainSocket = null; + } + } + + /** + * Pause accept new connections. + * + * @return void + */ + public function pauseAccept() + { + if (static::$globalEvent && false === $this->_pauseAccept && $this->_mainSocket) { + static::$globalEvent->del($this->_mainSocket, EventInterface::EV_READ); + $this->_pauseAccept = true; + } + } + + /** + * Resume accept new connections. + * + * @return void + */ + public function resumeAccept() + { + // Register a listener to be notified when server socket is ready to read. + if (static::$globalEvent && true === $this->_pauseAccept && $this->_mainSocket) { + if ($this->transport !== 'udp') { + static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptConnection')); + } else { + static::$globalEvent->add($this->_mainSocket, EventInterface::EV_READ, array($this, 'acceptUdpConnection')); + } + $this->_pauseAccept = false; + } + } + + /** + * Get socket name. + * + * @return string + */ + public function getSocketName() + { + return $this->_socketName ? lcfirst($this->_socketName) : 'none'; + } + + /** + * Run worker instance. + * + * @return void + */ + public function run() + { + //Update process state. + static::$_status = static::STATUS_RUNNING; + + // Register shutdown function for checking errors. + register_shutdown_function(array("\\Workerman\\Worker", 'checkErrors')); + + // Set autoload root path. + Autoloader::setRootPath($this->_autoloadRootPath); + + // Create a global event loop. + if (!static::$globalEvent) { + $event_loop_class = static::getEventLoopName(); + static::$globalEvent = new $event_loop_class; + $this->resumeAccept(); + } + + // Reinstall signal. + static::reinstallSignal(); + + // Init Timer. + Timer::init(static::$globalEvent); + + // Set an empty onMessage callback. + if (empty($this->onMessage)) { + $this->onMessage = function () {}; + } + + restore_error_handler(); + + // Try to emit onWorkerStart callback. + if ($this->onWorkerStart) { + try { + call_user_func($this->onWorkerStart, $this); + } catch (\Exception $e) { + static::log($e); + // Avoid rapid infinite loop exit. + sleep(1); + exit(250); + } catch (\Error $e) { + static::log($e); + // Avoid rapid infinite loop exit. + sleep(1); + exit(250); + } + } + + // Main loop. + static::$globalEvent->loop(); + } + + /** + * Stop current worker instance. + * + * @return void + */ + public function stop() + { + // Try to emit onWorkerStop callback. + if ($this->onWorkerStop) { + try { + call_user_func($this->onWorkerStop, $this); + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + } + // Remove listener for server socket. + $this->unlisten(); + // Close all connections for the worker. + if (!static::$_gracefulStop) { + foreach ($this->connections as $connection) { + $connection->close(); + } + } + // Clear callback. + $this->onMessage = $this->onClose = $this->onError = $this->onBufferDrain = $this->onBufferFull = null; + } + + /** + * Accept a connection. + * + * @param resource $socket + * @return void + */ + public function acceptConnection($socket) + { + // Accept a connection on server socket. + set_error_handler(function(){}); + $new_socket = stream_socket_accept($socket, 0, $remote_address); + restore_error_handler(); + + // Thundering herd. + if (!$new_socket) { + return; + } + + // TcpConnection. + $connection = new TcpConnection($new_socket, $remote_address); + $this->connections[$connection->id] = $connection; + $connection->worker = $this; + $connection->protocol = $this->protocol; + $connection->transport = $this->transport; + $connection->onMessage = $this->onMessage; + $connection->onClose = $this->onClose; + $connection->onError = $this->onError; + $connection->onBufferDrain = $this->onBufferDrain; + $connection->onBufferFull = $this->onBufferFull; + + // Try to emit onConnect callback. + if ($this->onConnect) { + try { + call_user_func($this->onConnect, $connection); + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + } + } + + /** + * For udp package. + * + * @param resource $socket + * @return bool + */ + public function acceptUdpConnection($socket) + { + set_error_handler(function(){}); + $recv_buffer = stream_socket_recvfrom($socket, static::MAX_UDP_PACKAGE_SIZE, 0, $remote_address); + restore_error_handler(); + if (false === $recv_buffer || empty($remote_address)) { + return false; + } + // UdpConnection. + $connection = new UdpConnection($socket, $remote_address); + $connection->protocol = $this->protocol; + if ($this->onMessage) { + try { + if ($this->protocol !== null) { + /** @var \Workerman\Protocols\ProtocolInterface $parser */ + $parser = $this->protocol; + if(method_exists($parser,'input')){ + while($recv_buffer !== ''){ + $len = $parser::input($recv_buffer, $connection); + if($len == 0) + return true; + $package = substr($recv_buffer,0,$len); + $recv_buffer = substr($recv_buffer,$len); + $data = $parser::decode($package,$connection); + if ($data === false) + continue; + call_user_func($this->onMessage, $connection, $data); + } + }else{ + $data = $parser::decode($recv_buffer, $connection); + // Discard bad packets. + if ($data === false) + return true; + call_user_func($this->onMessage, $connection, $data); + } + }else{ + call_user_func($this->onMessage, $connection, $recv_buffer); + } + ConnectionInterface::$statistics['total_request']++; + } catch (\Exception $e) { + static::log($e); + exit(250); + } catch (\Error $e) { + static::log($e); + exit(250); + } + } + return true; + } +} diff --git a/bot/workerman/composer.json b/bot/workerman/composer.json new file mode 100755 index 0000000..fdd4808 --- /dev/null +++ b/bot/workerman/composer.json @@ -0,0 +1,38 @@ +{ + "name": "workerman/workerman", + "type": "library", + "keywords": [ + "event-loop", + "asynchronous" + ], + "homepage": "http://www.workerman.net", + "license": "MIT", + "description": "An asynchronous event driven PHP framework for easily building fast, scalable network applications.", + "authors": [ + { + "name": "walkor", + "email": "walkor@workerman.net", + "homepage": "http://www.workerman.net", + "role": "Developer" + } + ], + "support": { + "email": "walkor@workerman.net", + "issues": "https://github.com/walkor/workerman/issues", + "forum": "http://wenda.workerman.net/", + "wiki": "http://doc.workerman.net/", + "source": "https://github.com/walkor/workerman" + }, + "require": { + "php": ">=5.3" + }, + "suggest": { + "ext-event": "For better performance. " + }, + "autoload": { + "psr-4": { + "Workerman\\": "./" + } + }, + "minimum-stability": "dev" +} diff --git a/gateway/.env.example b/gateway/.env.example new file mode 100755 index 0000000..041ae7f --- /dev/null +++ b/gateway/.env.example @@ -0,0 +1,13 @@ +APP_NAME=入魔入佛 +APP_URL=http://127.0.0.1:8000 +TIMEZONE=Asia/Shanghai +DB_HOST=127.0.0.1 +DB_DATABASE=xxxx +DB_USERNAME=root +DB_PASSWORD=xxxxx +GATEWAY_PORT=4000 +GATEWAY_REGISTER=4001 +GATEWAY_START=4002 +GATEWAY_PING='{"action":"ping"}' +GATEWAY_PANG='{"action":"pang"}' +GATEWAY_CLOSE=/api/Gateway/close diff --git a/gateway/.gitignore b/gateway/.gitignore new file mode 100644 index 0000000..d060848 --- /dev/null +++ b/gateway/.gitignore @@ -0,0 +1,6 @@ +/vendor +composer.lock +/.idea +/.vscode +.env + diff --git a/gateway/Applications/Lib/Db.php b/gateway/Applications/Lib/Db.php new file mode 100644 index 0000000..54f7213 --- /dev/null +++ b/gateway/Applications/Lib/Db.php @@ -0,0 +1,139 @@ +db = new \PDO($dsn, $data['dbuser'], $data['dbpassword']); + $this->db->query('set character set utf8mb4;'); + $this->db->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false); + } + + public static function get($key = ''): Db + { + $database['dbname'] = Tool::ini($key . 'DB_DATABASE'); + $database['dbuser'] = Tool::ini($key . 'DB_USERNAME'); + $database['dbpassword'] = Tool::ini($key . 'DB_PASSWORD'); + $database['dbhost'] = Tool::ini($key . 'DB_HOST'); + if (!isset(self::$instance[$database['dbname']]) || !self::$instance[$database['dbname']] instanceof self) { + self::$instance[$database['dbname']] = new Db($database); + } else { + try { + //断线重连 + self::$instance[$database['dbname']]->db->getAttribute(\PDO::ATTR_SERVER_INFO); + } catch (\PDOException $e) { + if (strpos($e->getMessage(), 'MySQL server has gone away') !== false) { + self::$instance[$database['dbname']] = new Db($database); + } + } + } + + return self::$instance[$database['dbname']]; + } + + + /*********PDO**********/ + public function count($sql, $parameters = null): int + { + return $this->exeupdate($sql, $parameters); + } + + public function querysql($sql, $parameters = null) + { + return $this->exeupdate($sql, $parameters); + } + + + public function querysqlinsertid($sql, $parameters = null) + { + if ($this->exeupdate($sql, $parameters)) { + return $this->db->lastInsertId(); + } else { + return 0; + } + } + + + public function getRow($sql, $parameters = null) + { + $res = $this->exequery($sql, $parameters); + if (count($res) > 0) { + + return $res[0]; + } else { + return array(); + } + } + + public function getAll($sql, $parameters = null) + { + $res = $this->exequery($sql, $parameters); + if (count($res) > 0) { + return $res; + } else { + return array(); + } + } + + public function beginTransaction() + { + $this->db->beginTransaction(); + } + + public function rollback() + { + $this->db->rollback(); + } + + public function commit() + { + $this->db->commit(); + } + + public function exequery($sql, $parameters = null) + { + $conn = $this->db; + $stmt = $conn->prepare($sql); + $stmt->execute($parameters); + $rs = $stmt->fetchall(\PDO::FETCH_ASSOC); + $stmt = null; + $conn = null; + return $rs; + } + + public function exeupdate($sql, $parameters = null) + { + $stmt = $this->db->prepare($sql); + $stmt->execute($parameters); + $affectedrows = $stmt->rowcount(); + $stmt = null; + $conn = null; + return $affectedrows; + } + + public function checklink() + { + $res = $this->db->getAttribute(\PDO::ATTR_SERVER_INFO); + if (strpos($res, 'server has gone away') !== false) { + $this->db = null; + return false; + } else { + return true; + } + } + + public function getinsertid() + { + return $this->db->lastInsertId(); + } + + public function close() + { + return $this->db = null; + } +} diff --git a/gateway/Applications/Lib/Db2.php b/gateway/Applications/Lib/Db2.php new file mode 100644 index 0000000..29890a6 --- /dev/null +++ b/gateway/Applications/Lib/Db2.php @@ -0,0 +1,42 @@ + $value) { + $sql .= "`{$key}`,"; + $sql1 .= "?,"; + $insertArr[] = $value; + } + $sql = trim($sql, ','); + $sql1 = trim($sql1, ','); + $sql .= ")" . $sql1 . ")"; + $db->querysql($sql, $insertArr); + return $db->getinsertid(); + } + + public static function u($db, $table, $array, $where, $where_a = []) + { + $updateArr = array(); + $sql = "update `{$table}` set "; + foreach ($array as $key => $value) { + $sql .= "`{$key}`=?,"; + $updateArr[] = $value; + } + $sql = trim($sql, ','); + $where = ' ' . $where; + $sql .= $where; + $updateArr = array_merge($updateArr, $where_a); + return $db->querysql($sql, $updateArr); + } + + public static function d($db, $table, $where, $where_a = []) + { + $sql = "delete from `{$table}` " . $where; + $db->querysql($sql, $where_a); + } +} diff --git a/gateway/Applications/Lib/Tool.php b/gateway/Applications/Lib/Tool.php new file mode 100644 index 0000000..b6ed1e9 --- /dev/null +++ b/gateway/Applications/Lib/Tool.php @@ -0,0 +1,88 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +/** + * 用于检测业务代码死循环或者长时间阻塞等问题 + * 如果发现业务卡死,可以将下面declare打开(去掉//注释),并执行php start.php reload + * 然后观察一段时间workerman.log看是否有process_timeout异常 + */ + +//declare(ticks=1); +require_once __DIR__ . '/../../Applications/Lib/Tool.php'; +require_once __DIR__ . '/../../Applications/Lib/Db.php'; +require_once __DIR__ . '/../../Applications/Lib/Db2.php'; +date_default_timezone_set(Tool::ini('TIMEZONE')); + +use \GatewayWorker\Lib\Gateway; + +/** + * 主逻辑 + * 主要是处理 onConnect onMessage onClose 三个方法 + * onConnect 和 onClose 如果不需要可以不用实现并删除 + */ +class Events +{ + /** + * 当客户端连接时触发 + * 如果业务不需此回调可以删除onConnect + * + * @param int $client_id 连接id + */ + public static function onConnect($client_id) + { + echo json_encode(['CONNECT', date('Y-m-d H:i:s'), $client_id], JSON_UNESCAPED_UNICODE) . "\n"; + Gateway::sendToClient($client_id, json_encode([ + 'action' => 'init', + 'client_id' => $client_id + ], JSON_UNESCAPED_UNICODE)); + } + + /** + * 当客户端发来消息时触发 + * @param int $client_id 连接id + * @param mixed $message 具体消息 + */ + public static function onMessage($client_id, $message) + { + // $db = Db::get(); + if ($message == Tool::ini('GATEWAY_PING')) { + echo json_encode(['PING', date('Y-m-d H:i:s'), $client_id], JSON_UNESCAPED_UNICODE) . "\n"; + Gateway::sendToClient($client_id, Tool::ini('GATEWAY_PANG')); + } + } + + /** + * 当用户断开连接时触发 + * @param int $client_id 连接id + */ + public static function onClose($client_id) + { + echo json_encode(['CLOSE', date('Y-m-d H:i:s'), $client_id], JSON_UNESCAPED_UNICODE) . "\n"; + if (!!Tool::ini('GATEWAY_CLOSE')) self::post(Tool::ini('APP_URL') . Tool::ini('GATEWAY_CLOSE'), ['client' => $client_id]); + } + + public static function post($url, $data, $type = 'json') + { + $curl = curl_init(); + curl_setopt($curl, CURLOPT_URL, $url); + curl_setopt($curl, CURLOPT_RETURNTRANSFER, true); + curl_setopt($curl, CURLOPT_POST, true); + if ($type === 'data') { + curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false); + curl_setopt($curl, CURLOPT_POSTFIELDS, $data); + curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true); + } + if ($type === 'json') { + $data_string = json_encode($data, JSON_UNESCAPED_UNICODE); + curl_setopt($curl, CURLOPT_HTTPHEADER, [ + 'Content-Type: application/json; charset=utf-8', + 'Content-Length: ' . strlen($data_string) + ]); + curl_setopt($curl, CURLOPT_POSTFIELDS, $data_string); + } + $r = curl_exec($curl); + curl_close($curl); + return $r; + } +} diff --git a/gateway/Applications/YourApp/start_businessworker.php b/gateway/Applications/YourApp/start_businessworker.php new file mode 100644 index 0000000..9f16911 --- /dev/null +++ b/gateway/Applications/YourApp/start_businessworker.php @@ -0,0 +1,34 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use \Workerman\Worker; +use \GatewayWorker\BusinessWorker; + +// 自动加载类 +require_once __DIR__ . '/../../vendor/autoload.php'; +require_once __DIR__ . '/../../Applications/Lib/Tool.php'; +// bussinessWorker 进程 +$worker = new BusinessWorker(); +// worker名称 +$worker->name = Tool::ini("APP_NAME") . 'Business'; +// bussinessWorker进程数量 +$worker->count = 1; +// 服务注册地址 +$worker->registerAddress = '127.0.0.1:' . Tool::ini("GATEWAY_REGISTER"); + +// 如果不是在根目录启动,则运行runAll方法 +if (!defined('GLOBAL_START')) { + Worker::runAll(); +} + diff --git a/gateway/Applications/YourApp/start_gateway.php b/gateway/Applications/YourApp/start_gateway.php new file mode 100644 index 0000000..7b1cb78 --- /dev/null +++ b/gateway/Applications/YourApp/start_gateway.php @@ -0,0 +1,63 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use \Workerman\Worker; +use \GatewayWorker\Gateway; + +// 自动加载类 +require_once __DIR__ . '/../../vendor/autoload.php'; +require_once __DIR__ . '/../../Applications/Lib/Tool.php'; +// gateway 进程,这里使用Text协议,可以用telnet测试 +$url = 'websocket://0.0.0.0:' . Tool::ini('GATEWAY_PORT'); +$gateway = new Gateway($url); +// gateway名称,status方便查看 +$gateway->name = Tool::ini('APP_NAME'); +// gateway进程数 +$gateway->count = 1; +// 本机ip,分布式部署时使用内网ip +$gateway->lanIp = '127.0.0.1'; +// 内部通讯起始端口,假如$gateway->count=4,起始端口为4000 +// 则一般会使用4000 4001 4002 4003 4个端口作为内部通讯端口 +$gateway->startPort = Tool::ini('GATEWAY_START'); +// 服务注册地址 +$gateway->registerAddress = '127.0.0.1:' . Tool::ini('GATEWAY_REGISTER'); + +// 心跳间隔 +$gateway->pingInterval = 60; +// 心跳数据 +$gateway->pingData = Tool::ini('GATEWAY_PING'); + +/* +// 当客户端连接上来时,设置连接的onWebSocketConnect,即在websocket握手时的回调 +$gateway->onConnect = function($connection) +{ + $connection->onWebSocketConnect = function($connection , $http_header) + { + // 可以在这里判断连接来源是否合法,不合法就关掉连接 + // $_SERVER['HTTP_ORIGIN']标识来自哪个站点的页面发起的websocket链接 + if($_SERVER['HTTP_ORIGIN'] != 'http://kedou.workerman.net') + { + $connection->close(); + } + // onWebSocketConnect 里面$_GET $_SERVER是可用的 + // var_dump($_GET, $_SERVER); + }; +}; +*/ + +// 如果不是在根目录启动,则运行runAll方法 +if (!defined('GLOBAL_START')) { + Worker::runAll(); +} + diff --git a/gateway/Applications/YourApp/start_register.php b/gateway/Applications/YourApp/start_register.php new file mode 100644 index 0000000..bb760dd --- /dev/null +++ b/gateway/Applications/YourApp/start_register.php @@ -0,0 +1,28 @@ + + * @copyright walkor + * @link http://www.workerman.net/ + * @license http://www.opensource.org/licenses/mit-license.php MIT License + */ + +use \Workerman\Worker; +use \GatewayWorker\Register; + +// 自动加载类 +require_once __DIR__ . '/../../vendor/autoload.php'; +require_once __DIR__ . '/../../Applications/Lib/Tool.php'; +// register 必须是text协议 +$register = new Register('text://0.0.0.0:' . Tool::ini('GATEWAY_REGISTER')); + +// 如果不是在根目录启动,则运行runAll方法 +if (!defined('GLOBAL_START')) { + Worker::runAll(); +} + diff --git a/gateway/MIT-LICENSE.txt b/gateway/MIT-LICENSE.txt new file mode 100644 index 0000000..fd6b1c8 --- /dev/null +++ b/gateway/MIT-LICENSE.txt @@ -0,0 +1,21 @@ +The MIT License + +Copyright (c) 2009-2015 walkor and contributors (see https://github.com/walkor/workerman/contributors) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/gateway/README.md b/gateway/README.md new file mode 100644 index 0000000..b6a43a5 --- /dev/null +++ b/gateway/README.md @@ -0,0 +1,37 @@ +GatewayClient +================= +`composer install` + +`composer require workerman/gatewayclient` + +```php +use GatewayClient\Gateway; + +Gateway::$registerAddress = '127.0.0.1:3238'; + +Gateway::bindUid($client_id, $uid); +Gateway::joinGroup($client_id, $group_id); + +Gateway::sendToUid($uid, $message); +Gateway::sendToGroup($group, $message); + +Gateway::sendToAll($data); +Gateway::sendToClient($client_id, $data); +Gateway::closeClient($client_id); +Gateway::isOnline($client_id); +Gateway::bindUid($client_id, $uid); +Gateway::isUidOnline($uid); +Gateway::getClientIdByUid($uid); +Gateway::unbindUid($client_id, $uid); +Gateway::sendToUid($uid, $data); +Gateway::joinGroup($client_id, $group); +Gateway::sendToGroup($group, $data); +Gateway::leaveGroup($client_id, $group); +Gateway::getClientCountByGroup($group); +Gateway::getClientSessionsByGroup($group); +Gateway::getAllClientCount(); +Gateway::getAllClientSessions(); +Gateway::setSession($client_id, $session); +Gateway::updateSession($client_id, $session); +Gateway::getSession($client_id); +``` \ No newline at end of file diff --git a/gateway/composer.json b/gateway/composer.json new file mode 100644 index 0000000..4e90c94 --- /dev/null +++ b/gateway/composer.json @@ -0,0 +1,15 @@ +{ + "name" : "workerman/gateway-worker-demo", + "keywords": ["distributed","communication"], + "homepage": "http://www.workerman.net", + "license" : "MIT", + "require": { + "workerman/gateway-worker" : ">=3.0.0" + }, + "autoload": { + "psr-4": { + "" : "./", + "" : "./Applications/YourApp/" + } + } +} diff --git a/gateway/start.bat b/gateway/start.bat new file mode 100644 index 0000000..14bd09f --- /dev/null +++ b/gateway/start.bat @@ -0,0 +1,2 @@ +php Applications\YourApp\start_register.php Applications\YourApp\start_gateway.php Applications\YourApp\start_businessworker.php +pause \ No newline at end of file diff --git a/gateway/start.php b/gateway/start.php new file mode 100644 index 0000000..410c580 --- /dev/null +++ b/gateway/start.php @@ -0,0 +1,37 @@ + + export default { + onLaunch: function() { + console.log(`\n %c 鹿和 %c https://sa0.online/ \n\n`, 'color: #ffffff; background: #fd6b60; padding:5px 0;', + 'color: #fd6b60;background: #ffffff; padding:5px 0;') + } + } + + + \ No newline at end of file diff --git a/uniapp/api/index.js b/uniapp/api/index.js new file mode 100644 index 0000000..d833f7f --- /dev/null +++ b/uniapp/api/index.js @@ -0,0 +1,60 @@ +import { + $post +} from '@/lu/axios.js' +import $config from '@/config.js' +const app_path = 'App' +let url = '' +let gzh = { + id: '', + jump: '', + auth: '' +} +const urlPick = () => { + if ($config.config.length > 0) { + url = $config.config[0].url + gzh = $config.config[0].gzh + for (let i in $config.config) { + if (!!$config.config[i].active) { + url = $config.config[i].url + gzh = $config.config[i].gzh + break + } + } + } +} +urlPick() + +export const GzhConfig = gzh +export const WeChatPayPayTestAction = async (data) => await $post({ + url: `${url}/api/Test/WeChatPay/pay_test`, + data +}) +export const WeChatLoginTestAction = async (data) => await $post({ + url: `${url}/api/Test/WeChat/login_test`, + data +}) +export const yo = async (data) => await $post({ + url: `${url}/api/yo`, + data +}) + +export const $image = (path) => { + const path_ret = ['http://', 'https://', ';base64,'] + for (let i = 0; i < path_ret.length; i++) { + if (path.indexOf(path_ret[i]) !== -1) { + return path + } + } + return `${$config.url}${path}` +} + +export const $response = (response, then, error = () => {}) => { + if (response) { + if (response.code != 200) { + uni.$lu.toast(response.message); + error() + return + } + then() + } +} \ No newline at end of file diff --git a/uniapp/components/dev/dev.vue b/uniapp/components/dev/dev.vue new file mode 100644 index 0000000..78d0767 --- /dev/null +++ b/uniapp/components/dev/dev.vue @@ -0,0 +1,170 @@ + + + \ No newline at end of file diff --git a/uniapp/config.js b/uniapp/config.js new file mode 100644 index 0000000..32e985f --- /dev/null +++ b/uniapp/config.js @@ -0,0 +1,31 @@ +const config = [{ + active: true, + url: 'http://lucode3.sa0.online', + gzh: { + id: '', + jump: 'http://127.0.0.1:5173/h5/#/pages/gzh/login/login?', + auth: 'https://127.0.0.1:5173/open/Gzh/auth/{gzh.id}' + }, +}, { + active: false, + url: 'http://127.0.0.1:8000', + gzh: { + id: '', + jump: 'http://127.0.0.1:5173/h5/#/pages/gzh/login/login?', + auth: 'https://127.0.0.1:5173/open/Gzh/auth/{gzh.id}' + }, +}] +uni.$config = JSON.parse(JSON.stringify(config)) +const config_str_key = "CONFIG_STR" +let config_str = uni.getStorageSync(config_str_key) +if (!config_str) { + config_str = JSON.stringify(config) + uni.setStorageSync(config_str_key, config_str) +} +const config_data = JSON.parse(config_str) +export default { + title: '入魔入佛', + app_id: 'wx0d92d2990ec16a55', + config: config_data, + token: '9E84F543-15CB-4E29-8009-DA0CAE9CB2C3' +} \ No newline at end of file diff --git a/uniapp/index.html b/uniapp/index.html new file mode 100644 index 0000000..c3ff205 --- /dev/null +++ b/uniapp/index.html @@ -0,0 +1,20 @@ + + + + + + + + + + +
+ + + diff --git a/uniapp/lu/axios.js b/uniapp/lu/axios.js new file mode 100644 index 0000000..aec3ddf --- /dev/null +++ b/uniapp/lu/axios.js @@ -0,0 +1,24 @@ +import { + getToken +} from './token.js' + +export const $post = async ({ + url, + data = {} +}) => { + const token = getToken() ? getToken() : ''; + const res = await uni.request({ + url, + method: 'POST', + data, + header: { + Authorization: 'Bearer ' + token + } + }); + if (!!res && res.data != '') { + return res.data + } else { + uni.$lu.toast("请求发生错误") + return false + } +} \ No newline at end of file diff --git a/uniapp/lu/format.js b/uniapp/lu/format.js new file mode 100644 index 0000000..ab0dd20 --- /dev/null +++ b/uniapp/lu/format.js @@ -0,0 +1,17 @@ +const format = (value = Date.now(), format = "Y-M-D h:m:s") => { + const formatNumber = n => `0${n}`.slice(-2); + const date = new Date(value); + const formatList = ["Y", "M", "D", "h", "m", "s"]; + const resultList = []; + resultList.push(date.getFullYear().toString()); + resultList.push(formatNumber(date.getMonth() + 1)); + resultList.push(formatNumber(date.getDate())); + resultList.push(formatNumber(date.getHours())); + resultList.push(formatNumber(date.getMinutes())); + resultList.push(formatNumber(date.getSeconds())); + for (let i = 0; i < resultList.length; i++) { + format = format.replace(formatList[i], resultList[i]); + } + return format; +} +export default format diff --git a/uniapp/lu/index.js b/uniapp/lu/index.js new file mode 100644 index 0000000..af0fe90 --- /dev/null +++ b/uniapp/lu/index.js @@ -0,0 +1,8 @@ +import config from '@/config.js' +import toast from './toast.js'; +import format from './format.js'; +export default { + toast, + format, + config, +}; \ No newline at end of file diff --git a/uniapp/lu/toast.js b/uniapp/lu/toast.js new file mode 100644 index 0000000..e437edd --- /dev/null +++ b/uniapp/lu/toast.js @@ -0,0 +1,9 @@ +const toast = (title, duration = 1500) => { + uni.showToast({ + title: title, + icon: 'none', + duration: duration + }) +} + +export default toast diff --git a/uniapp/lu/token.js b/uniapp/lu/token.js new file mode 100644 index 0000000..0124348 --- /dev/null +++ b/uniapp/lu/token.js @@ -0,0 +1,11 @@ +import $config from '@/config.js' +const TokenKey = $config.token; +export function getToken() { + return uni.getStorageSync(TokenKey); +} +export function setToken(token) { + uni.setStorageSync(TokenKey, token); +} +export function delToken() { + uni.removeStorageSync(TokenKey); +} \ No newline at end of file diff --git a/uniapp/main.js b/uniapp/main.js new file mode 100644 index 0000000..04ab6c3 --- /dev/null +++ b/uniapp/main.js @@ -0,0 +1,16 @@ +import { + createSSRApp +} from 'vue' +import * as Pinia from 'pinia'; +import App from './App.vue' +import $lu from './lu' + +uni.$lu = $lu +export function createApp() { + const app = createSSRApp(App) + app.use(Pinia.createPinia()) + return { + app, + Pinia + } +} diff --git a/uniapp/manifest.json b/uniapp/manifest.json new file mode 100644 index 0000000..0f4457a --- /dev/null +++ b/uniapp/manifest.json @@ -0,0 +1,65 @@ +{ + "name" : "鹿和开发模板", + "appid" : "__UNI__54F7FDE", + "description" : "", + "versionName" : "1.0.0", + "versionCode" : "100", + "transformPx" : false, + "app-plus" : { + /* 5+App特有相关 */ + "usingComponents" : true, + "nvueCompiler" : "uni-app", + "nvueStyleCompiler" : "uni-app", + "splashscreen" : { + "alwaysShowBeforeRender" : true, + "waiting" : true, + "autoclose" : true, + "delay" : 0 + }, + "modules" : {}, + /* 模块配置 */ + "distribute" : { + /* 应用发布信息 */ + "android" : { + /* android打包配置 */ + "permissions" : [ + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "", + "" + ] + }, + "ios" : {}, + /* ios打包配置 */ + "sdkConfigs" : {} + } + }, + /* SDK配置 */ + "quickapp" : {}, + /* 快应用特有相关 */ + "mp-weixin" : { + "appid" : "wx0d92d2990ec16a55", + "setting" : { + "urlCheck" : false + }, + "usingComponents" : true + }, + "vueVersion" : "3", + "h5" : { + "router" : { + "base" : "/h5/" + }, + "title" : "鹿和开发模板" + } +} diff --git a/uniapp/pages.json b/uniapp/pages.json new file mode 100644 index 0000000..0794097 --- /dev/null +++ b/uniapp/pages.json @@ -0,0 +1,69 @@ +{ + "pages": [{ + "path": "pages/main/index/index", + "style": { + "navigationBarTitleText": "鹿和开发模板", + "navigationStyle": "custom" + } + }, { + "path": "pages/main/my/my", + "style": { + "navigationBarTitleText": "我的" + } + }], + "subPackages": [{ + "root": "pages/dev", + "pages": [{ + "path": "example/example", + "style": { + "navigationBarTitleText": "示例", + "enablePullDownRefresh": false, + "navigationStyle": "custom" + } + }, { + "path": "proxy/proxy", + "style": { + "navigationBarTitleText": "代理设置", + "enablePullDownRefresh": false + } + + }] + }, { + "root": "pages/gzh", + "pages": [{ + "path": "login/login", + "style": { + "navigationBarTitleText": "公众号登录跳转", + "enablePullDownRefresh": false, + "navigationStyle": "custom" + } + + }] + }], + "tabBar": { + "color": "#7A7E83", + "selectedColor": "#333333", + "borderStyle": "white", + "backgroundColor": "#ffffff", + "list": [{ + "pagePath": "pages/main/index/index", + "iconPath": "static/tabbar/tof.png", + "selectedIconPath": "static/tabbar/ton.png", + "text": "首页" + }, { + "pagePath": "pages/main/my/my", + "iconPath": "static/tabbar/tof.png", + "selectedIconPath": "static/tabbar/ton.png", + "text": "我的" + }] + }, + "globalStyle": { + "navigationBarTextStyle": "black", + "navigationBarTitleText": "鹿和开发套件", + "navigationBarBackgroundColor": "#F8F8F8", + "backgroundColor": "#F8F8F8", + "app-plus": { + "background": "#efeff4" + } + } +} \ No newline at end of file diff --git a/uniapp/pages/dev/example/Api/Api.vue b/uniapp/pages/dev/example/Api/Api.vue new file mode 100644 index 0000000..b38f2bd --- /dev/null +++ b/uniapp/pages/dev/example/Api/Api.vue @@ -0,0 +1,30 @@ + + diff --git a/uniapp/pages/dev/example/Navbar/Navbar.vue b/uniapp/pages/dev/example/Navbar/Navbar.vue new file mode 100644 index 0000000..103d2ec --- /dev/null +++ b/uniapp/pages/dev/example/Navbar/Navbar.vue @@ -0,0 +1,15 @@ + + diff --git a/uniapp/pages/dev/example/PayTest/PayTest.vue b/uniapp/pages/dev/example/PayTest/PayTest.vue new file mode 100644 index 0000000..7996289 --- /dev/null +++ b/uniapp/pages/dev/example/PayTest/PayTest.vue @@ -0,0 +1,77 @@ + + + + + + diff --git a/uniapp/uni_modules/uni-easyinput/package.json b/uniapp/uni_modules/uni-easyinput/package.json new file mode 100644 index 0000000..bd128e1 --- /dev/null +++ b/uniapp/uni_modules/uni-easyinput/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-easyinput", + "displayName": "uni-easyinput 增强输入框", + "version": "1.1.9", + "description": "Easyinput 组件是对原生input组件的增强", + "keywords": [ + "uni-ui", + "uniui", + "input", + "uni-easyinput", + "输入框" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-easyinput/readme.md b/uniapp/uni_modules/uni-easyinput/readme.md new file mode 100644 index 0000000..f1faf8f --- /dev/null +++ b/uniapp/uni_modules/uni-easyinput/readme.md @@ -0,0 +1,11 @@ + + +### Easyinput 增强输入框 +> **组件名:uni-easyinput** +> 代码块: `uEasyinput` + + +easyinput 组件是对原生input组件的增强 ,是专门为配合表单组件[uni-forms](https://ext.dcloud.net.cn/plugin?id=2773)而设计的,easyinput 内置了边框,图标等,同时包含 input 所有功能 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-easyinput) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-fab/changelog.md b/uniapp/uni_modules/uni-fab/changelog.md new file mode 100644 index 0000000..9bd4729 --- /dev/null +++ b/uniapp/uni_modules/uni-fab/changelog.md @@ -0,0 +1,23 @@ +## 1.2.5(2023-03-29) +- 新增 pattern.icon 属性,可自定义图标 +## 1.2.4(2022-09-07) +小程序端由于 style 使用了对象导致报错,[详情](https://ask.dcloud.net.cn/question/152790?item_id=211778&rf=false) +## 1.2.3(2022-09-05) +- 修复 nvue 环境下,具有 tabBar 时,fab 组件下部位置无法正常获取 --window-bottom 的bug,详见:[https://ask.dcloud.net.cn/question/110638?notification_id=826310](https://ask.dcloud.net.cn/question/110638?notification_id=826310) +## 1.2.2(2021-12-29) +- 更新 组件依赖 +## 1.2.1(2021-11-19) +- 修复 阴影颜色不正确的bug +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-fab](https://uniapp.dcloud.io/component/uniui/uni-fab) +## 1.1.1(2021-11-09) +- 新增 提供组件设计资源,组件样式调整 +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-02-05) +- 调整为uni_modules目录规范 +- 优化 按钮背景色调整 +- 优化 兼容pc端 diff --git a/uniapp/uni_modules/uni-fab/components/uni-fab/uni-fab.vue b/uniapp/uni_modules/uni-fab/components/uni-fab/uni-fab.vue new file mode 100644 index 0000000..dfa65c1 --- /dev/null +++ b/uniapp/uni_modules/uni-fab/components/uni-fab/uni-fab.vue @@ -0,0 +1,491 @@ + + + + + diff --git a/uniapp/uni_modules/uni-fab/package.json b/uniapp/uni_modules/uni-fab/package.json new file mode 100644 index 0000000..18c0810 --- /dev/null +++ b/uniapp/uni_modules/uni-fab/package.json @@ -0,0 +1,84 @@ +{ + "id": "uni-fab", + "displayName": "uni-fab 悬浮按钮", + "version": "1.2.5", + "description": "悬浮按钮 fab button ,点击可展开一个图标按钮菜单。", + "keywords": [ + "uni-ui", + "uniui", + "按钮", + "悬浮按钮", + "fab" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss","uni-icons"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uniapp/uni_modules/uni-fab/readme.md b/uniapp/uni_modules/uni-fab/readme.md new file mode 100644 index 0000000..9a444e8 --- /dev/null +++ b/uniapp/uni_modules/uni-fab/readme.md @@ -0,0 +1,9 @@ +## Fab 悬浮按钮 +> **组件名:uni-fab** +> 代码块: `uFab` + + +点击可展开一个图形按钮菜单 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-fab) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-fav/changelog.md b/uniapp/uni_modules/uni-fav/changelog.md new file mode 100644 index 0000000..d8a08d4 --- /dev/null +++ b/uniapp/uni_modules/uni-fav/changelog.md @@ -0,0 +1,19 @@ +## 1.2.1(2022-05-30) +- 新增 stat 属性 ,是否开启uni统计功能 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-fav](https://uniapp.dcloud.io/component/uniui/uni-fav) +## 1.1.1(2021-08-24) +- 新增 支持国际化 +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.6(2021-05-12) +- 新增 组件示例地址 +## 1.0.5(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.4(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.0.3(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.0.2(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-fav/components/uni-fav/i18n/en.json b/uniapp/uni_modules/uni-fav/components/uni-fav/i18n/en.json new file mode 100644 index 0000000..9a0759e --- /dev/null +++ b/uniapp/uni_modules/uni-fav/components/uni-fav/i18n/en.json @@ -0,0 +1,4 @@ +{ + "uni-fav.collect": "collect", + "uni-fav.collected": "collected" +} diff --git a/uniapp/uni_modules/uni-fav/components/uni-fav/i18n/index.js b/uniapp/uni_modules/uni-fav/components/uni-fav/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uniapp/uni_modules/uni-fav/components/uni-fav/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hans.json b/uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hans.json new file mode 100644 index 0000000..67c89bf --- /dev/null +++ b/uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hans.json @@ -0,0 +1,4 @@ +{ + "uni-fav.collect": "收藏", + "uni-fav.collected": "已收藏" +} diff --git a/uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hant.json b/uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hant.json new file mode 100644 index 0000000..67c89bf --- /dev/null +++ b/uniapp/uni_modules/uni-fav/components/uni-fav/i18n/zh-Hant.json @@ -0,0 +1,4 @@ +{ + "uni-fav.collect": "收藏", + "uni-fav.collected": "已收藏" +} diff --git a/uniapp/uni_modules/uni-fav/components/uni-fav/uni-fav.vue b/uniapp/uni_modules/uni-fav/components/uni-fav/uni-fav.vue new file mode 100644 index 0000000..d2c58df --- /dev/null +++ b/uniapp/uni_modules/uni-fav/components/uni-fav/uni-fav.vue @@ -0,0 +1,161 @@ + + + + + diff --git a/uniapp/uni_modules/uni-fav/package.json b/uniapp/uni_modules/uni-fav/package.json new file mode 100644 index 0000000..cc14697 --- /dev/null +++ b/uniapp/uni_modules/uni-fav/package.json @@ -0,0 +1,89 @@ +{ + "id": "uni-fav", + "displayName": "uni-fav 收藏按钮", + "version": "1.2.1", + "description": " Fav 收藏组件,可自定义颜色、大小。", + "keywords": [ + "fav", + "uni-ui", + "uniui", + "收藏" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uniapp/uni_modules/uni-fav/readme.md b/uniapp/uni_modules/uni-fav/readme.md new file mode 100644 index 0000000..4de125d --- /dev/null +++ b/uniapp/uni_modules/uni-fav/readme.md @@ -0,0 +1,10 @@ + + +## Fav 收藏按钮 +> **组件名:uni-fav** +> 代码块: `uFav` + +用于收藏功能,可点击切换选中、不选中的状态。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-fav) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-file-picker/changelog.md b/uniapp/uni_modules/uni-file-picker/changelog.md new file mode 100644 index 0000000..b8230e9 --- /dev/null +++ b/uniapp/uni_modules/uni-file-picker/changelog.md @@ -0,0 +1,67 @@ +## 1.0.4(2023-03-29) +- 修复 手动上传删除一个文件后不能再上传的bug +## 1.0.3(2022-12-19) +- 新增 sourceType 属性, 可以自定义图片和视频选择的来源 +## 1.0.2(2022-07-04) +- 修复 在uni-forms下样式不生效的bug +## 1.0.1(2021-11-23) +- 修复 参数为对象的情况下,url在某些情况显示错误的bug +## 1.0.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-file-picker](https://uniapp.dcloud.io/component/uniui/uni-file-picker) +## 0.2.16(2021-11-08) +- 修复 传入空对象 ,显示错误的Bug +## 0.2.15(2021-08-30) +- 修复 return-type="object" 时且存在v-model时,无法删除文件的Bug +## 0.2.14(2021-08-23) +- 新增 参数中返回 fileID 字段 +## 0.2.13(2021-08-23) +- 修复 腾讯云传入fileID 不能回显的bug +- 修复 选择图片后,不能放大的问题 +## 0.2.12(2021-08-17) +- 修复 由于 0.2.11 版本引起的不能回显图片的Bug +## 0.2.11(2021-08-16) +- 新增 clearFiles(index) 方法,可以手动删除指定文件 +- 修复 v-model 值设为 null 报错的Bug +## 0.2.10(2021-08-13) +- 修复 return-type="object" 时,无法删除文件的Bug +## 0.2.9(2021-08-03) +- 修复 auto-upload 属性失效的Bug +## 0.2.8(2021-07-31) +- 修复 fileExtname属性不指定值报错的Bug +## 0.2.7(2021-07-31) +- 修复 在某种场景下图片不回显的Bug +## 0.2.6(2021-07-30) +- 修复 return-type为object下,返回值不正确的Bug +## 0.2.5(2021-07-30) +- 修复(重要) H5 平台下如果和uni-forms组件一同使用导致页面卡死的问题 +## 0.2.3(2021-07-28) +- 优化 调整示例代码 +## 0.2.2(2021-07-27) +- 修复 vue3 下赋值错误的Bug +- 优化 h5平台下上传文件导致页面卡死的问题 +## 0.2.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 0.1.1(2021-07-02) +- 修复 sourceType 缺少默认值导致 ios 无法选择文件 +## 0.1.0(2021-06-30) +- 优化 解耦与uniCloud的强绑定关系 ,如不绑定服务空间,默认autoUpload为false且不可更改 +## 0.0.11(2021-06-30) +- 修复 由 0.0.10 版本引发的 returnType 属性失效的问题 +## 0.0.10(2021-06-29) +- 优化 文件上传后进度条消失时机 +## 0.0.9(2021-06-29) +- 修复 在uni-forms 中,删除文件 ,获取的值不对的Bug +## 0.0.8(2021-06-15) +- 修复 删除文件时无法触发 v-model 的Bug +## 0.0.7(2021-05-12) +- 新增 组件示例地址 +## 0.0.6(2021-04-09) +- 修复 选择的文件非 file-extname 字段指定的扩展名报错的Bug +## 0.0.5(2021-04-09) +- 优化 更新组件示例 +## 0.0.4(2021-04-09) +- 优化 file-extname 字段支持字符串写法,多个扩展名需要用逗号分隔 +## 0.0.3(2021-02-05) +- 调整为uni_modules目录规范 +- 修复 微信小程序不指定 fileExtname 属性选择失败的Bug diff --git a/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js b/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js new file mode 100644 index 0000000..aff0864 --- /dev/null +++ b/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/choose-and-upload-file.js @@ -0,0 +1,224 @@ +'use strict'; + +const ERR_MSG_OK = 'chooseAndUploadFile:ok'; +const ERR_MSG_FAIL = 'chooseAndUploadFile:fail'; + +function chooseImage(opts) { + const { + count, + sizeType = ['original', 'compressed'], + sourceType, + extension + } = opts + return new Promise((resolve, reject) => { + uni.chooseImage({ + count, + sizeType, + sourceType, + extension, + success(res) { + resolve(normalizeChooseAndUploadFileRes(res, 'image')); + }, + fail(res) { + reject({ + errMsg: res.errMsg.replace('chooseImage:fail', ERR_MSG_FAIL), + }); + }, + }); + }); +} + +function chooseVideo(opts) { + const { + camera, + compressed, + maxDuration, + sourceType, + extension + } = opts; + return new Promise((resolve, reject) => { + uni.chooseVideo({ + camera, + compressed, + maxDuration, + sourceType, + extension, + success(res) { + const { + tempFilePath, + duration, + size, + height, + width + } = res; + resolve(normalizeChooseAndUploadFileRes({ + errMsg: 'chooseVideo:ok', + tempFilePaths: [tempFilePath], + tempFiles: [ + { + name: (res.tempFile && res.tempFile.name) || '', + path: tempFilePath, + size, + type: (res.tempFile && res.tempFile.type) || '', + width, + height, + duration, + fileType: 'video', + cloudPath: '', + }, ], + }, 'video')); + }, + fail(res) { + reject({ + errMsg: res.errMsg.replace('chooseVideo:fail', ERR_MSG_FAIL), + }); + }, + }); + }); +} + +function chooseAll(opts) { + const { + count, + extension + } = opts; + return new Promise((resolve, reject) => { + let chooseFile = uni.chooseFile; + if (typeof wx !== 'undefined' && + typeof wx.chooseMessageFile === 'function') { + chooseFile = wx.chooseMessageFile; + } + if (typeof chooseFile !== 'function') { + return reject({ + errMsg: ERR_MSG_FAIL + ' 请指定 type 类型,该平台仅支持选择 image 或 video。', + }); + } + chooseFile({ + type: 'all', + count, + extension, + success(res) { + resolve(normalizeChooseAndUploadFileRes(res)); + }, + fail(res) { + reject({ + errMsg: res.errMsg.replace('chooseFile:fail', ERR_MSG_FAIL), + }); + }, + }); + }); +} + +function normalizeChooseAndUploadFileRes(res, fileType) { + res.tempFiles.forEach((item, index) => { + if (!item.name) { + item.name = item.path.substring(item.path.lastIndexOf('/') + 1); + } + if (fileType) { + item.fileType = fileType; + } + item.cloudPath = + Date.now() + '_' + index + item.name.substring(item.name.lastIndexOf('.')); + }); + if (!res.tempFilePaths) { + res.tempFilePaths = res.tempFiles.map((file) => file.path); + } + return res; +} + +function uploadCloudFiles(files, max = 5, onUploadProgress) { + files = JSON.parse(JSON.stringify(files)) + const len = files.length + let count = 0 + let self = this + return new Promise(resolve => { + while (count < max) { + next() + } + + function next() { + let cur = count++ + if (cur >= len) { + !files.find(item => !item.url && !item.errMsg) && resolve(files) + return + } + const fileItem = files[cur] + const index = self.files.findIndex(v => v.uuid === fileItem.uuid) + fileItem.url = '' + delete fileItem.errMsg + + uniCloud + .uploadFile({ + filePath: fileItem.path, + cloudPath: fileItem.cloudPath, + fileType: fileItem.fileType, + onUploadProgress: res => { + res.index = index + onUploadProgress && onUploadProgress(res) + } + }) + .then(res => { + fileItem.url = res.fileID + fileItem.index = index + if (cur < len) { + next() + } + }) + .catch(res => { + fileItem.errMsg = res.errMsg || res.message + fileItem.index = index + if (cur < len) { + next() + } + }) + } + }) +} + + + + + +function uploadFiles(choosePromise, { + onChooseFile, + onUploadProgress +}) { + return choosePromise + .then((res) => { + if (onChooseFile) { + const customChooseRes = onChooseFile(res); + if (typeof customChooseRes !== 'undefined') { + return Promise.resolve(customChooseRes).then((chooseRes) => typeof chooseRes === 'undefined' ? + res : chooseRes); + } + } + return res; + }) + .then((res) => { + if (res === false) { + return { + errMsg: ERR_MSG_OK, + tempFilePaths: [], + tempFiles: [], + }; + } + return res + }) +} + +function chooseAndUploadFile(opts = { + type: 'all' +}) { + if (opts.type === 'image') { + return uploadFiles(chooseImage(opts), opts); + } + else if (opts.type === 'video') { + return uploadFiles(chooseVideo(opts), opts); + } + return uploadFiles(chooseAll(opts), opts); +} + +export { + chooseAndUploadFile, + uploadCloudFiles +}; diff --git a/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue b/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue new file mode 100644 index 0000000..370441d --- /dev/null +++ b/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/uni-file-picker.vue @@ -0,0 +1,667 @@ + + + + + diff --git a/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue b/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue new file mode 100644 index 0000000..625d92e --- /dev/null +++ b/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-file.vue @@ -0,0 +1,325 @@ + + + + + diff --git a/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue b/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue new file mode 100644 index 0000000..2a29bc2 --- /dev/null +++ b/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/upload-image.vue @@ -0,0 +1,292 @@ + + + + + diff --git a/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/utils.js b/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/utils.js new file mode 100644 index 0000000..60aaa3e --- /dev/null +++ b/uniapp/uni_modules/uni-file-picker/components/uni-file-picker/utils.js @@ -0,0 +1,109 @@ +/** + * 获取文件名和后缀 + * @param {String} name + */ +export const get_file_ext = (name) => { + const last_len = name.lastIndexOf('.') + const len = name.length + return { + name: name.substring(0, last_len), + ext: name.substring(last_len + 1, len) + } +} + +/** + * 获取扩展名 + * @param {Array} fileExtname + */ +export const get_extname = (fileExtname) => { + if (!Array.isArray(fileExtname)) { + let extname = fileExtname.replace(/(\[|\])/g, '') + return extname.split(',') + } else { + return fileExtname + } + return [] +} + +/** + * 获取文件和检测是否可选 + */ +export const get_files_and_is_max = (res, _extname) => { + let filePaths = [] + let files = [] + if(!_extname || _extname.length === 0){ + return { + filePaths, + files + } + } + res.tempFiles.forEach(v => { + let fileFullName = get_file_ext(v.name) + const extname = fileFullName.ext.toLowerCase() + if (_extname.indexOf(extname) !== -1) { + files.push(v) + filePaths.push(v.path) + } + }) + if (files.length !== res.tempFiles.length) { + uni.showToast({ + title: `当前选择了${res.tempFiles.length}个文件 ,${res.tempFiles.length - files.length} 个文件格式不正确`, + icon: 'none', + duration: 5000 + }) + } + + return { + filePaths, + files + } +} + + +/** + * 获取图片信息 + * @param {Object} filepath + */ +export const get_file_info = (filepath) => { + return new Promise((resolve, reject) => { + uni.getImageInfo({ + src: filepath, + success(res) { + resolve(res) + }, + fail(err) { + reject(err) + } + }) + }) +} +/** + * 获取封装数据 + */ +export const get_file_data = async (files, type = 'image') => { + // 最终需要上传数据库的数据 + let fileFullName = get_file_ext(files.name) + const extname = fileFullName.ext.toLowerCase() + let filedata = { + name: files.name, + uuid: files.uuid, + extname: extname || '', + cloudPath: files.cloudPath, + fileType: files.fileType, + url: files.path || files.path, + size: files.size, //单位是字节 + image: {}, + path: files.path, + video: {} + } + if (type === 'image') { + const imageinfo = await get_file_info(files.path) + delete filedata.video + filedata.image.width = imageinfo.width + filedata.image.height = imageinfo.height + filedata.image.location = imageinfo.path + } else { + delete filedata.image + } + return filedata +} diff --git a/uniapp/uni_modules/uni-file-picker/package.json b/uniapp/uni_modules/uni-file-picker/package.json new file mode 100644 index 0000000..35c926c --- /dev/null +++ b/uniapp/uni_modules/uni-file-picker/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-file-picker", + "displayName": "uni-file-picker 文件选择上传", + "version": "1.0.4", + "description": "文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间", + "keywords": [ + "uni-ui", + "uniui", + "图片上传", + "文件上传" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uniapp/uni_modules/uni-file-picker/readme.md b/uniapp/uni_modules/uni-file-picker/readme.md new file mode 100644 index 0000000..c8399a5 --- /dev/null +++ b/uniapp/uni_modules/uni-file-picker/readme.md @@ -0,0 +1,11 @@ + +## FilePicker 文件选择上传 + +> **组件名:uni-file-picker** +> 代码块: `uFilePicker` + + +文件选择上传组件,可以选择图片、视频等任意文件并上传到当前绑定的服务空间 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-file-picker) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-forms/changelog.md b/uniapp/uni_modules/uni-forms/changelog.md new file mode 100644 index 0000000..8218df5 --- /dev/null +++ b/uniapp/uni_modules/uni-forms/changelog.md @@ -0,0 +1,92 @@ +## 1.4.9(2023-02-10) +- 修复 required 参数无法动态绑定 +## 1.4.8(2022-08-23) +- 优化 根据 rules 自动添加 required 的问题 +## 1.4.7(2022-08-22) +- 修复 item 未设置 require 属性,rules 设置 require 后,星号也显示的 bug,详见:[https://ask.dcloud.net.cn/question/151540](https://ask.dcloud.net.cn/question/151540) +## 1.4.6(2022-07-13) +- 修复 model 需要校验的值没有声明对应字段时,导致第一次不触发校验的bug +## 1.4.5(2022-07-05) +- 新增 更多表单示例 +- 优化 子表单组件过期提示的问题 +- 优化 子表单组件uni-datetime-picker、uni-data-select、uni-data-picker的显示样式 +## 1.4.4(2022-07-04) +- 更新 删除组件日志 +## 1.4.3(2022-07-04) +- 修复 由 1.4.0 引发的 label 插槽不生效的bug +## 1.4.2(2022-07-04) +- 修复 子组件找不到 setValue 报错的bug +## 1.4.1(2022-07-04) +- 修复 uni-data-picker 在 uni-forms-item 中报错的bug +- 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug +## 1.4.0(2022-06-30) +- 【重要】组件逻辑重构,部分用法用旧版本不兼容,请注意兼容问题 +- 【重要】组件使用 Provide/Inject 方式注入依赖,提供了自定义表单组件调用 uni-forms 校验表单的能力 +- 新增 model 属性,等同于原 value/modelValue 属性,旧属性即将废弃 +- 新增 validateTrigger 属性的 blur 值,仅 uni-easyinput 生效 +- 新增 onFieldChange 方法,可以对子表单进行校验,可替代binddata方法 +- 新增 子表单的 setRules 方法,配合自定义校验函数使用 +- 新增 uni-forms-item 的 setRules 方法,配置动态表单使用可动态更新校验规则 +- 优化 动态表单校验方式,废弃拼接name的方式 +## 1.3.3(2022-06-22) +- 修复 表单校验顺序无序问题 +## 1.3.2(2021-12-09) +- +## 1.3.1(2021-11-19) +- 修复 label 插槽不生效的bug +## 1.3.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-forms](https://uniapp.dcloud.io/component/uniui/uni-forms) +## 1.2.7(2021-08-13) +- 修复 没有添加校验规则的字段依然报错的Bug +## 1.2.6(2021-08-11) +- 修复 重置表单错误信息无法清除的问题 +## 1.2.5(2021-08-11) +- 优化 组件文档 +## 1.2.4(2021-08-11) +- 修复 表单验证只生效一次的问题 +## 1.2.3(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.2.2(2021-07-26) +- 修复 vue2 下条件编译导致destroyed生命周期失效的Bug +- 修复 1.2.1 引起的示例在小程序平台报错的Bug +## 1.2.1(2021-07-22) +- 修复 动态校验表单,默认值为空的情况下校验失效的Bug +- 修复 不指定name属性时,运行报错的Bug +- 优化 label默认宽度从65调整至70,使required为true且四字时不换行 +- 优化 组件示例,新增动态校验示例代码 +- 优化 组件文档,使用方式更清晰 +## 1.2.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.2(2021-06-25) +- 修复 pattern 属性在微信小程序平台无效的问题 +## 1.1.1(2021-06-22) +- 修复 validate-trigger属性为submit且err-show-type属性为toast时不能弹出的Bug +## 1.1.0(2021-06-22) +- 修复 只写setRules方法而导致校验不生效的Bug +- 修复 由上个办法引发的错误提示文字错位的Bug +## 1.0.48(2021-06-21) +- 修复 不设置 label 属性 ,无法设置label插槽的问题 +## 1.0.47(2021-06-21) +- 修复 不设置label属性,label-width属性不生效的bug +- 修复 setRules 方法与rules属性冲突的问题 +## 1.0.46(2021-06-04) +- 修复 动态删减数据导致报错的问题 +## 1.0.45(2021-06-04) +- 新增 modelValue 属性 ,value 即将废弃 +## 1.0.44(2021-06-02) +- 新增 uni-forms-item 可以设置单独的 rules +- 新增 validate 事件增加 keepitem 参数,可以选择那些字段不过滤 +- 优化 submit 事件重命名为 validate +## 1.0.43(2021-05-12) +- 新增 组件示例地址 +## 1.0.42(2021-04-30) +- 修复 自定义检验器失效的问题 +## 1.0.41(2021-03-05) +- 更新 校验器 +- 修复 表单规则设置类型为 number 的情况下,值为0校验失败的Bug +## 1.0.40(2021-03-04) +- 修复 动态显示uni-forms-item的情况下,submit 方法获取值错误的Bug +## 1.0.39(2021-02-05) +- 调整为uni_modules目录规范 +- 修复 校验器传入 int 等类型 ,返回String类型的Bug diff --git a/uniapp/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue b/uniapp/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue new file mode 100644 index 0000000..91fe351 --- /dev/null +++ b/uniapp/uni_modules/uni-forms/components/uni-forms-item/uni-forms-item.vue @@ -0,0 +1,627 @@ + + + + + diff --git a/uniapp/uni_modules/uni-forms/components/uni-forms/uni-forms.vue b/uniapp/uni_modules/uni-forms/components/uni-forms/uni-forms.vue new file mode 100644 index 0000000..ed2f6d9 --- /dev/null +++ b/uniapp/uni_modules/uni-forms/components/uni-forms/uni-forms.vue @@ -0,0 +1,397 @@ + + + + + diff --git a/uniapp/uni_modules/uni-forms/components/uni-forms/utils.js b/uniapp/uni_modules/uni-forms/components/uni-forms/utils.js new file mode 100644 index 0000000..6da2421 --- /dev/null +++ b/uniapp/uni_modules/uni-forms/components/uni-forms/utils.js @@ -0,0 +1,293 @@ +/** + * 简单处理对象拷贝 + * @param {Obejct} 被拷贝对象 + * @@return {Object} 拷贝对象 + */ +export const deepCopy = (val) => { + return JSON.parse(JSON.stringify(val)) +} +/** + * 过滤数字类型 + * @param {String} format 数字类型 + * @@return {Boolean} 返回是否为数字类型 + */ +export const typeFilter = (format) => { + return format === 'int' || format === 'double' || format === 'number' || format === 'timestamp'; +} + +/** + * 把 value 转换成指定的类型,用于处理初始值,原因是初始值需要入库不能为 undefined + * @param {String} key 字段名 + * @param {any} value 字段值 + * @param {Object} rules 表单校验规则 + */ +export const getValue = (key, value, rules) => { + const isRuleNumType = rules.find(val => val.format && typeFilter(val.format)); + const isRuleBoolType = rules.find(val => (val.format && val.format === 'boolean') || val.format === 'bool'); + // 输入类型为 number + if (!!isRuleNumType) { + if (!value && value !== 0) { + value = null + } else { + value = isNumber(Number(value)) ? Number(value) : value + } + } + + // 输入类型为 boolean + if (!!isRuleBoolType) { + value = isBoolean(value) ? value : false + } + + return value; +} + +/** + * 获取表单数据 + * @param {String|Array} name 真实名称,需要使用 realName 获取 + * @param {Object} data 原始数据 + * @param {any} value 需要设置的值 + */ +export const setDataValue = (field, formdata, value) => { + formdata[field] = value + return value || '' +} + +/** + * 获取表单数据 + * @param {String|Array} field 真实名称,需要使用 realName 获取 + * @param {Object} data 原始数据 + */ +export const getDataValue = (field, data) => { + return objGet(data, field) +} + +/** + * 获取表单类型 + * @param {String|Array} field 真实名称,需要使用 realName 获取 + */ +export const getDataValueType = (field, data) => { + const value = getDataValue(field, data) + return { + type: type(value), + value + } +} + +/** + * 获取表单可用的真实name + * @param {String|Array} name 表单name + * @@return {String} 表单可用的真实name + */ +export const realName = (name, data = {}) => { + const base_name = _basePath(name) + if (typeof base_name === 'object' && Array.isArray(base_name) && base_name.length > 1) { + const realname = base_name.reduce((a, b) => a += `#${b}`, '_formdata_') + return realname + } + return base_name[0] || name +} + +/** + * 判断是否表单可用的真实name + * @param {String|Array} name 表单name + * @@return {String} 表单可用的真实name + */ +export const isRealName = (name) => { + const reg = /^_formdata_#*/ + return reg.test(name) +} + +/** + * 获取表单数据的原始格式 + * @@return {Object|Array} object 需要解析的数据 + */ +export const rawData = (object = {}, name) => { + let newData = JSON.parse(JSON.stringify(object)) + let formData = {} + for(let i in newData){ + let path = name2arr(i) + objSet(formData,path,newData[i]) + } + return formData +} + +/** + * 真实name还原为 array + * @param {*} name + */ +export const name2arr = (name) => { + let field = name.replace('_formdata_#', '') + field = field.split('#').map(v => (isNumber(v) ? Number(v) : v)) + return field +} + +/** + * 对象中设置值 + * @param {Object|Array} object 源数据 + * @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c'] + * @param {String} value 需要设置的值 + */ +export const objSet = (object, path, value) => { + if (typeof object !== 'object') return object; + _basePath(path).reduce((o, k, i, _) => { + if (i === _.length - 1) { + // 若遍历结束直接赋值 + o[k] = value + return null + } else if (k in o) { + // 若存在对应路径,则返回找到的对象,进行下一次遍历 + return o[k] + } else { + // 若不存在对应路径,则创建对应对象,若下一路径是数字,新对象赋值为空数组,否则赋值为空对象 + o[k] = /^[0-9]{1,}$/.test(_[i + 1]) ? [] : {} + return o[k] + } + }, object) + // 返回object + return object; +} + +// 处理 path, path有三种形式:'a[0].b.c'、'a.0.b.c' 和 ['a','0','b','c'],需要统一处理成数组,便于后续使用 +function _basePath(path) { + // 若是数组,则直接返回 + if (Array.isArray(path)) return path + // 若有 '[',']',则替换成将 '[' 替换成 '.',去掉 ']' + return path.replace(/\[/g, '.').replace(/\]/g, '').split('.') +} + +/** + * 从对象中获取值 + * @param {Object|Array} object 源数据 + * @param {String| Array} path 'a.b.c' 或 ['a',0,'b','c'] + * @param {String} defaultVal 如果无法从调用链中获取值的默认值 + */ +export const objGet = (object, path, defaultVal = 'undefined') => { + // 先将path处理成统一格式 + let newPath = _basePath(path) + // 递归处理,返回最后结果 + let val = newPath.reduce((o, k) => { + return (o || {})[k] + }, object); + return !val || val !== undefined ? val : defaultVal +} + + +/** + * 是否为 number 类型 + * @param {any} num 需要判断的值 + * @return {Boolean} 是否为 number + */ +export const isNumber = (num) => { + return !isNaN(Number(num)) +} + +/** + * 是否为 boolean 类型 + * @param {any} bool 需要判断的值 + * @return {Boolean} 是否为 boolean + */ +export const isBoolean = (bool) => { + return (typeof bool === 'boolean') +} +/** + * 是否有必填字段 + * @param {Object} rules 规则 + * @return {Boolean} 是否有必填字段 + */ +export const isRequiredField = (rules) => { + let isNoField = false; + for (let i = 0; i < rules.length; i++) { + const ruleData = rules[i]; + if (ruleData.required) { + isNoField = true; + break; + } + } + return isNoField; +} + + +/** + * 获取数据类型 + * @param {Any} obj 需要获取数据类型的值 + */ +export const type = (obj) => { + var class2type = {}; + + // 生成class2type映射 + "Boolean Number String Function Array Date RegExp Object Error".split(" ").map(function(item, index) { + class2type["[object " + item + "]"] = item.toLowerCase(); + }) + if (obj == null) { + return obj + ""; + } + return typeof obj === "object" || typeof obj === "function" ? + class2type[Object.prototype.toString.call(obj)] || "object" : + typeof obj; +} + +/** + * 判断两个值是否相等 + * @param {any} a 值 + * @param {any} b 值 + * @return {Boolean} 是否相等 + */ +export const isEqual = (a, b) => { + //如果a和b本来就全等 + if (a === b) { + //判断是否为0和-0 + return a !== 0 || 1 / a === 1 / b; + } + //判断是否为null和undefined + if (a == null || b == null) { + return a === b; + } + //接下来判断a和b的数据类型 + var classNameA = toString.call(a), + classNameB = toString.call(b); + //如果数据类型不相等,则返回false + if (classNameA !== classNameB) { + return false; + } + //如果数据类型相等,再根据不同数据类型分别判断 + switch (classNameA) { + case '[object RegExp]': + case '[object String]': + //进行字符串转换比较 + return '' + a === '' + b; + case '[object Number]': + //进行数字转换比较,判断是否为NaN + if (+a !== +a) { + return +b !== +b; + } + //判断是否为0或-0 + return +a === 0 ? 1 / +a === 1 / b : +a === +b; + case '[object Date]': + case '[object Boolean]': + return +a === +b; + } + //如果是对象类型 + if (classNameA == '[object Object]') { + //获取a和b的属性长度 + var propsA = Object.getOwnPropertyNames(a), + propsB = Object.getOwnPropertyNames(b); + if (propsA.length != propsB.length) { + return false; + } + for (var i = 0; i < propsA.length; i++) { + var propName = propsA[i]; + //如果对应属性对应值不相等,则返回false + if (a[propName] !== b[propName]) { + return false; + } + } + return true; + } + //如果是数组类型 + if (classNameA == '[object Array]') { + if (a.toString() == b.toString()) { + return true; + } + return false; + } +} diff --git a/uniapp/uni_modules/uni-forms/components/uni-forms/validate.js b/uniapp/uni_modules/uni-forms/components/uni-forms/validate.js new file mode 100644 index 0000000..1834c6c --- /dev/null +++ b/uniapp/uni_modules/uni-forms/components/uni-forms/validate.js @@ -0,0 +1,486 @@ +var pattern = { + email: /^\S+?@\S+?\.\S+?$/, + idcard: /^[1-9]\d{5}(18|19|([23]\d))\d{2}((0[1-9])|(10|11|12))(([0-2][1-9])|10|20|30|31)\d{3}[0-9Xx]$/, + url: new RegExp( + "^(?!mailto:)(?:(?:http|https|ftp)://|//)(?:\\S+(?::\\S*)?@)?(?:(?:(?:[1-9]\\d?|1\\d\\d|2[01]\\d|22[0-3])(?:\\.(?:1?\\d{1,2}|2[0-4]\\d|25[0-5])){2}(?:\\.(?:[0-9]\\d?|1\\d\\d|2[0-4]\\d|25[0-4]))|(?:(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)(?:\\.(?:[a-z\\u00a1-\\uffff0-9]+-*)*[a-z\\u00a1-\\uffff0-9]+)*(?:\\.(?:[a-z\\u00a1-\\uffff]{2,})))|localhost)(?::\\d{2,5})?(?:(/|\\?|#)[^\\s]*)?$", + 'i') +}; + +const FORMAT_MAPPING = { + "int": 'integer', + "bool": 'boolean', + "double": 'number', + "long": 'number', + "password": 'string' + // "fileurls": 'array' +} + +function formatMessage(args, resources = '') { + var defaultMessage = ['label'] + defaultMessage.forEach((item) => { + if (args[item] === undefined) { + args[item] = '' + } + }) + + let str = resources + for (let key in args) { + let reg = new RegExp('{' + key + '}') + str = str.replace(reg, args[key]) + } + return str +} + +function isEmptyValue(value, type) { + if (value === undefined || value === null) { + return true; + } + + if (typeof value === 'string' && !value) { + return true; + } + + if (Array.isArray(value) && !value.length) { + return true; + } + + if (type === 'object' && !Object.keys(value).length) { + return true; + } + + return false; +} + +const types = { + integer(value) { + return types.number(value) && parseInt(value, 10) === value; + }, + string(value) { + return typeof value === 'string'; + }, + number(value) { + if (isNaN(value)) { + return false; + } + return typeof value === 'number'; + }, + "boolean": function(value) { + return typeof value === 'boolean'; + }, + "float": function(value) { + return types.number(value) && !types.integer(value); + }, + array(value) { + return Array.isArray(value); + }, + object(value) { + return typeof value === 'object' && !types.array(value); + }, + date(value) { + return value instanceof Date; + }, + timestamp(value) { + if (!this.integer(value) || Math.abs(value).toString().length > 16) { + return false + } + return true; + }, + file(value) { + return typeof value.url === 'string'; + }, + email(value) { + return typeof value === 'string' && !!value.match(pattern.email) && value.length < 255; + }, + url(value) { + return typeof value === 'string' && !!value.match(pattern.url); + }, + pattern(reg, value) { + try { + return new RegExp(reg).test(value); + } catch (e) { + return false; + } + }, + method(value) { + return typeof value === 'function'; + }, + idcard(value) { + return typeof value === 'string' && !!value.match(pattern.idcard); + }, + 'url-https'(value) { + return this.url(value) && value.startsWith('https://'); + }, + 'url-scheme'(value) { + return value.startsWith('://'); + }, + 'url-web'(value) { + return false; + } +} + +class RuleValidator { + + constructor(message) { + this._message = message + } + + async validateRule(fieldKey, fieldValue, value, data, allData) { + var result = null + + let rules = fieldValue.rules + + let hasRequired = rules.findIndex((item) => { + return item.required + }) + if (hasRequired < 0) { + if (value === null || value === undefined) { + return result + } + if (typeof value === 'string' && !value.length) { + return result + } + } + + var message = this._message + + if (rules === undefined) { + return message['default'] + } + + for (var i = 0; i < rules.length; i++) { + let rule = rules[i] + let vt = this._getValidateType(rule) + + Object.assign(rule, { + label: fieldValue.label || `["${fieldKey}"]` + }) + + if (RuleValidatorHelper[vt]) { + result = RuleValidatorHelper[vt](rule, value, message) + if (result != null) { + break + } + } + + if (rule.validateExpr) { + let now = Date.now() + let resultExpr = rule.validateExpr(value, allData, now) + if (resultExpr === false) { + result = this._getMessage(rule, rule.errorMessage || this._message['default']) + break + } + } + + if (rule.validateFunction) { + result = await this.validateFunction(rule, value, data, allData, vt) + if (result !== null) { + break + } + } + } + + if (result !== null) { + result = message.TAG + result + } + + return result + } + + async validateFunction(rule, value, data, allData, vt) { + let result = null + try { + let callbackMessage = null + const res = await rule.validateFunction(rule, value, allData || data, (message) => { + callbackMessage = message + }) + if (callbackMessage || (typeof res === 'string' && res) || res === false) { + result = this._getMessage(rule, callbackMessage || res, vt) + } + } catch (e) { + result = this._getMessage(rule, e.message, vt) + } + return result + } + + _getMessage(rule, message, vt) { + return formatMessage(rule, message || rule.errorMessage || this._message[vt] || message['default']) + } + + _getValidateType(rule) { + var result = '' + if (rule.required) { + result = 'required' + } else if (rule.format) { + result = 'format' + } else if (rule.arrayType) { + result = 'arrayTypeFormat' + } else if (rule.range) { + result = 'range' + } else if (rule.maximum !== undefined || rule.minimum !== undefined) { + result = 'rangeNumber' + } else if (rule.maxLength !== undefined || rule.minLength !== undefined) { + result = 'rangeLength' + } else if (rule.pattern) { + result = 'pattern' + } else if (rule.validateFunction) { + result = 'validateFunction' + } + return result + } +} + +const RuleValidatorHelper = { + required(rule, value, message) { + if (rule.required && isEmptyValue(value, rule.format || typeof value)) { + return formatMessage(rule, rule.errorMessage || message.required); + } + + return null + }, + + range(rule, value, message) { + const { + range, + errorMessage + } = rule; + + let list = new Array(range.length); + for (let i = 0; i < range.length; i++) { + const item = range[i]; + if (types.object(item) && item.value !== undefined) { + list[i] = item.value; + } else { + list[i] = item; + } + } + + let result = false + if (Array.isArray(value)) { + result = (new Set(value.concat(list)).size === list.length); + } else { + if (list.indexOf(value) > -1) { + result = true; + } + } + + if (!result) { + return formatMessage(rule, errorMessage || message['enum']); + } + + return null + }, + + rangeNumber(rule, value, message) { + if (!types.number(value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + let { + minimum, + maximum, + exclusiveMinimum, + exclusiveMaximum + } = rule; + let min = exclusiveMinimum ? value <= minimum : value < minimum; + let max = exclusiveMaximum ? value >= maximum : value > maximum; + + if (minimum !== undefined && min) { + return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMinimum ? + 'exclusiveMinimum' : 'minimum' + ]) + } else if (maximum !== undefined && max) { + return formatMessage(rule, rule.errorMessage || message['number'][exclusiveMaximum ? + 'exclusiveMaximum' : 'maximum' + ]) + } else if (minimum !== undefined && maximum !== undefined && (min || max)) { + return formatMessage(rule, rule.errorMessage || message['number'].range) + } + + return null + }, + + rangeLength(rule, value, message) { + if (!types.string(value) && !types.array(value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + let min = rule.minLength; + let max = rule.maxLength; + let val = value.length; + + if (min !== undefined && val < min) { + return formatMessage(rule, rule.errorMessage || message['length'].minLength) + } else if (max !== undefined && val > max) { + return formatMessage(rule, rule.errorMessage || message['length'].maxLength) + } else if (min !== undefined && max !== undefined && (val < min || val > max)) { + return formatMessage(rule, rule.errorMessage || message['length'].range) + } + + return null + }, + + pattern(rule, value, message) { + if (!types['pattern'](rule.pattern, value)) { + return formatMessage(rule, rule.errorMessage || message.pattern.mismatch); + } + + return null + }, + + format(rule, value, message) { + var customTypes = Object.keys(types); + var format = FORMAT_MAPPING[rule.format] ? FORMAT_MAPPING[rule.format] : (rule.format || rule.arrayType); + + if (customTypes.indexOf(format) > -1) { + if (!types[format](value)) { + return formatMessage(rule, rule.errorMessage || message.typeError); + } + } + + return null + }, + + arrayTypeFormat(rule, value, message) { + if (!Array.isArray(value)) { + return formatMessage(rule, rule.errorMessage || message.typeError); + } + + for (let i = 0; i < value.length; i++) { + const element = value[i]; + let formatResult = this.format(rule, element, message) + if (formatResult !== null) { + return formatResult + } + } + + return null + } +} + +class SchemaValidator extends RuleValidator { + + constructor(schema, options) { + super(SchemaValidator.message); + + this._schema = schema + this._options = options || null + } + + updateSchema(schema) { + this._schema = schema + } + + async validate(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidate(data, false, allData) + } + return result.length ? result[0] : null + } + + async validateAll(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidate(data, true, allData) + } + return result + } + + async validateUpdate(data, allData) { + let result = this._checkFieldInSchema(data) + if (!result) { + result = await this.invokeValidateUpdate(data, false, allData) + } + return result.length ? result[0] : null + } + + async invokeValidate(data, all, allData) { + let result = [] + let schema = this._schema + for (let key in schema) { + let value = schema[key] + let errorMessage = await this.validateRule(key, value, data[key], data, allData) + if (errorMessage != null) { + result.push({ + key, + errorMessage + }) + if (!all) break + } + } + return result + } + + async invokeValidateUpdate(data, all, allData) { + let result = [] + for (let key in data) { + let errorMessage = await this.validateRule(key, this._schema[key], data[key], data, allData) + if (errorMessage != null) { + result.push({ + key, + errorMessage + }) + if (!all) break + } + } + return result + } + + _checkFieldInSchema(data) { + var keys = Object.keys(data) + var keys2 = Object.keys(this._schema) + if (new Set(keys.concat(keys2)).size === keys2.length) { + return '' + } + + var noExistFields = keys.filter((key) => { + return keys2.indexOf(key) < 0; + }) + var errorMessage = formatMessage({ + field: JSON.stringify(noExistFields) + }, SchemaValidator.message.TAG + SchemaValidator.message['defaultInvalid']) + return [{ + key: 'invalid', + errorMessage + }] + } +} + +function Message() { + return { + TAG: "", + default: '验证错误', + defaultInvalid: '提交的字段{field}在数据库中并不存在', + validateFunction: '验证无效', + required: '{label}必填', + 'enum': '{label}超出范围', + timestamp: '{label}格式无效', + whitespace: '{label}不能为空', + typeError: '{label}类型无效', + date: { + format: '{label}日期{value}格式无效', + parse: '{label}日期无法解析,{value}无效', + invalid: '{label}日期{value}无效' + }, + length: { + minLength: '{label}长度不能少于{minLength}', + maxLength: '{label}长度不能超过{maxLength}', + range: '{label}必须介于{minLength}和{maxLength}之间' + }, + number: { + minimum: '{label}不能小于{minimum}', + maximum: '{label}不能大于{maximum}', + exclusiveMinimum: '{label}不能小于等于{minimum}', + exclusiveMaximum: '{label}不能大于等于{maximum}', + range: '{label}必须介于{minimum}and{maximum}之间' + }, + pattern: { + mismatch: '{label}格式不匹配' + } + }; +} + + +SchemaValidator.message = new Message(); + +export default SchemaValidator diff --git a/uniapp/uni_modules/uni-forms/package.json b/uniapp/uni_modules/uni-forms/package.json new file mode 100644 index 0000000..1925611 --- /dev/null +++ b/uniapp/uni_modules/uni-forms/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-forms", + "displayName": "uni-forms 表单", + "version": "1.4.9", + "description": "由输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据", + "keywords": [ + "uni-ui", + "表单", + "校验", + "表单校验", + "表单验证" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uniapp/uni_modules/uni-forms/readme.md b/uniapp/uni_modules/uni-forms/readme.md new file mode 100644 index 0000000..63d5a04 --- /dev/null +++ b/uniapp/uni_modules/uni-forms/readme.md @@ -0,0 +1,23 @@ + + +## Forms 表单 + +> **组件名:uni-forms** +> 代码块: `uForms`、`uni-forms-item` +> 关联组件:`uni-forms-item`、`uni-easyinput`、`uni-data-checkbox`、`uni-group`。 + + +uni-app的内置组件已经有了 `
`组件,用于提交表单内容。 + +然而几乎每个表单都需要做表单验证,为了方便做表单验证,减少重复开发,`uni ui` 又基于 ``组件封装了 ``组件,内置了表单验证功能。 + +`` 提供了 `rules`属性来描述校验规则、``子组件来包裹具体的表单项,以及给原生或三方组件提供了 `binddata()` 来设置表单值。 + +每个要校验的表单项,不管input还是checkbox,都必须放在``组件中,且一个``组件只能放置一个表单项。 + +``组件内部预留了显示error message的区域,默认是在表单项的底部。 + +另外,``组件下面的各个表单项,可以通过``包裹为不同的分组。同一``下的不同表单项目将聚拢在一起,同其他group保持垂直间距。``仅影响视觉效果。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-forms) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-goods-nav/changelog.md b/uniapp/uni_modules/uni-goods-nav/changelog.md new file mode 100644 index 0000000..c6264c6 --- /dev/null +++ b/uniapp/uni_modules/uni-goods-nav/changelog.md @@ -0,0 +1,18 @@ +## 1.2.1(2022-05-30) +- 新增 stat属性,是否开启uni统计功能 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-goods-nav](https://uniapp.dcloud.io/component/uniui/uni-goods-nav) +## 1.1.1(2021-08-24) +- 新增 支持国际化 +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.5(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/en.json b/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/en.json new file mode 100644 index 0000000..dcdba41 --- /dev/null +++ b/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/en.json @@ -0,0 +1,6 @@ +{ + "uni-goods-nav.options.shop": "shop", + "uni-goods-nav.options.cart": "cart", + "uni-goods-nav.buttonGroup.addToCart": "add to cart", + "uni-goods-nav.buttonGroup.buyNow": "buy now" +} diff --git a/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/index.js b/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hans.json b/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hans.json new file mode 100644 index 0000000..48ee344 --- /dev/null +++ b/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hans.json @@ -0,0 +1,6 @@ +{ + "uni-goods-nav.options.shop": "店铺", + "uni-goods-nav.options.cart": "购物车", + "uni-goods-nav.buttonGroup.addToCart": "加入购物车", + "uni-goods-nav.buttonGroup.buyNow": "立即购买" +} diff --git a/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hant.json b/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hant.json new file mode 100644 index 0000000..d0a0255 --- /dev/null +++ b/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/i18n/zh-Hant.json @@ -0,0 +1,6 @@ +{ + "uni-goods-nav.options.shop": "店鋪", + "uni-goods-nav.options.cart": "購物車", + "uni-goods-nav.buttonGroup.addToCart": "加入購物車", + "uni-goods-nav.buttonGroup.buyNow": "立即購買" +} diff --git a/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/uni-goods-nav.vue b/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/uni-goods-nav.vue new file mode 100644 index 0000000..8a16b17 --- /dev/null +++ b/uniapp/uni_modules/uni-goods-nav/components/uni-goods-nav/uni-goods-nav.vue @@ -0,0 +1,229 @@ + + + + + diff --git a/uniapp/uni_modules/uni-goods-nav/package.json b/uniapp/uni_modules/uni-goods-nav/package.json new file mode 100644 index 0000000..636e45e --- /dev/null +++ b/uniapp/uni_modules/uni-goods-nav/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-goods-nav", + "displayName": "uni-goods-nav 商品导航", + "version": "1.2.1", + "description": "商品导航组件主要用于电商类应用底部导航,可自定义加入购物车,购买等操作", + "keywords": [ + "uni-ui", + "uniui", + "商品导航" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uniapp/uni_modules/uni-goods-nav/readme.md b/uniapp/uni_modules/uni-goods-nav/readme.md new file mode 100644 index 0000000..07df93f --- /dev/null +++ b/uniapp/uni_modules/uni-goods-nav/readme.md @@ -0,0 +1,10 @@ + + +## GoodsNav 商品导航 +> **组件名:uni-goods-nav** +> 代码块: `uGoodsNav` + +商品加入购物车,立即购买等。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-goods-nav) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-grid/changelog.md b/uniapp/uni_modules/uni-grid/changelog.md new file mode 100644 index 0000000..d301166 --- /dev/null +++ b/uniapp/uni_modules/uni-grid/changelog.md @@ -0,0 +1,13 @@ +## 1.4.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-grid](https://uniapp.dcloud.io/component/uniui/uni-grid) +## 1.3.2(2021-11-09) +- 新增 提供组件设计资源,组件样式调整 +## 1.3.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.3.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.2.4(2021-05-12) +- 新增 组件示例地址 +## 1.2.3(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-grid/components/uni-grid-item/uni-grid-item.vue b/uniapp/uni_modules/uni-grid/components/uni-grid-item/uni-grid-item.vue new file mode 100644 index 0000000..20fd54e --- /dev/null +++ b/uniapp/uni_modules/uni-grid/components/uni-grid-item/uni-grid-item.vue @@ -0,0 +1,127 @@ + + + + + diff --git a/uniapp/uni_modules/uni-grid/components/uni-grid/uni-grid.vue b/uniapp/uni_modules/uni-grid/components/uni-grid/uni-grid.vue new file mode 100644 index 0000000..96a412f --- /dev/null +++ b/uniapp/uni_modules/uni-grid/components/uni-grid/uni-grid.vue @@ -0,0 +1,142 @@ + + + + + diff --git a/uniapp/uni_modules/uni-grid/package.json b/uniapp/uni_modules/uni-grid/package.json new file mode 100644 index 0000000..ccb2c91 --- /dev/null +++ b/uniapp/uni_modules/uni-grid/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-grid", + "displayName": "uni-grid 宫格", + "version": "1.4.0", + "description": "Grid 宫格组件,提供移动端常见的宫格布局,如九宫格。", + "keywords": [ + "uni-ui", + "uniui", + "九宫格", + "表格" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss","uni-icons"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uniapp/uni_modules/uni-grid/readme.md b/uniapp/uni_modules/uni-grid/readme.md new file mode 100644 index 0000000..0aa44cc --- /dev/null +++ b/uniapp/uni_modules/uni-grid/readme.md @@ -0,0 +1,11 @@ + + +## Grid 宫格 +> **组件名:uni-grid** +> 代码块: `uGrid` + + +宫格组件。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-grid) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-group/changelog.md b/uniapp/uni_modules/uni-group/changelog.md new file mode 100644 index 0000000..a7024fd --- /dev/null +++ b/uniapp/uni_modules/uni-group/changelog.md @@ -0,0 +1,16 @@ +## 1.2.2(2022-05-30) +- 新增 stat属性,是否开启uni统计功能 +## 1.2.1(2021-11-22) +- 修复 vue3中某些scss变量无法找到的问题 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-group](https://uniapp.dcloud.io/component/uniui/uni-group) +## 1.1.7(2021-11-08) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +- 优化 组件文档 +## 1.0.3(2021-05-12) +- 新增 组件示例地址 +## 1.0.2(2021-02-05) +- 调整为uni_modules目录规范 +- 优化 兼容 nvue 页面 diff --git a/uniapp/uni_modules/uni-group/components/uni-group/uni-group.vue b/uniapp/uni_modules/uni-group/components/uni-group/uni-group.vue new file mode 100644 index 0000000..3425ecd --- /dev/null +++ b/uniapp/uni_modules/uni-group/components/uni-group/uni-group.vue @@ -0,0 +1,134 @@ + + + + diff --git a/uniapp/uni_modules/uni-group/package.json b/uniapp/uni_modules/uni-group/package.json new file mode 100644 index 0000000..ea00a08 --- /dev/null +++ b/uniapp/uni_modules/uni-group/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-group", + "displayName": "uni-group 分组", + "version": "1.2.2", + "description": "分组组件可用于将组件用于分组,添加间隔,以产生明显的区块", + "keywords": [ + "uni-ui", + "uniui", + "group", + "分组", + "" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-group/readme.md b/uniapp/uni_modules/uni-group/readme.md new file mode 100644 index 0000000..bae67f4 --- /dev/null +++ b/uniapp/uni_modules/uni-group/readme.md @@ -0,0 +1,9 @@ + +## Group 分组 +> **组件名:uni-group** +> 代码块: `uGroup` + +分组组件可用于将组件分组,添加间隔,以产生明显的区块。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-group) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-icons/changelog.md b/uniapp/uni_modules/uni-icons/changelog.md new file mode 100644 index 0000000..6449885 --- /dev/null +++ b/uniapp/uni_modules/uni-icons/changelog.md @@ -0,0 +1,22 @@ +## 1.3.5(2022-01-24) +- 优化 size 属性可以传入不带单位的字符串数值 +## 1.3.4(2022-01-24) +- 优化 size 支持其他单位 +## 1.3.3(2022-01-17) +- 修复 nvue 有些图标不显示的bug,兼容老版本图标 +## 1.3.2(2021-12-01) +- 优化 示例可复制图标名称 +## 1.3.1(2021-11-23) +- 优化 兼容旧组件 type 值 +## 1.3.0(2021-11-19) +- 新增 更多图标 +- 优化 自定义图标使用方式 +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-icons](https://uniapp.dcloud.io/component/uniui/uni-icons) +## 1.1.7(2021-11-08) +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.5(2021-05-12) +- 新增 组件示例地址 +## 1.1.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-icons/components/uni-icons/icons.js b/uniapp/uni_modules/uni-icons/components/uni-icons/icons.js new file mode 100644 index 0000000..7889936 --- /dev/null +++ b/uniapp/uni_modules/uni-icons/components/uni-icons/icons.js @@ -0,0 +1,1169 @@ +export default { + "id": "2852637", + "name": "uniui图标库", + "font_family": "uniicons", + "css_prefix_text": "uniui-", + "description": "", + "glyphs": [ + { + "icon_id": "25027049", + "name": "yanse", + "font_class": "color", + "unicode": "e6cf", + "unicode_decimal": 59087 + }, + { + "icon_id": "25027048", + "name": "wallet", + "font_class": "wallet", + "unicode": "e6b1", + "unicode_decimal": 59057 + }, + { + "icon_id": "25015720", + "name": "settings-filled", + "font_class": "settings-filled", + "unicode": "e6ce", + "unicode_decimal": 59086 + }, + { + "icon_id": "25015434", + "name": "shimingrenzheng-filled", + "font_class": "auth-filled", + "unicode": "e6cc", + "unicode_decimal": 59084 + }, + { + "icon_id": "24934246", + "name": "shop-filled", + "font_class": "shop-filled", + "unicode": "e6cd", + "unicode_decimal": 59085 + }, + { + "icon_id": "24934159", + "name": "staff-filled-01", + "font_class": "staff-filled", + "unicode": "e6cb", + "unicode_decimal": 59083 + }, + { + "icon_id": "24932461", + "name": "VIP-filled", + "font_class": "vip-filled", + "unicode": "e6c6", + "unicode_decimal": 59078 + }, + { + "icon_id": "24932462", + "name": "plus_circle_fill", + "font_class": "plus-filled", + "unicode": "e6c7", + "unicode_decimal": 59079 + }, + { + "icon_id": "24932463", + "name": "folder_add-filled", + "font_class": "folder-add-filled", + "unicode": "e6c8", + "unicode_decimal": 59080 + }, + { + "icon_id": "24932464", + "name": "yanse-filled", + "font_class": "color-filled", + "unicode": "e6c9", + "unicode_decimal": 59081 + }, + { + "icon_id": "24932465", + "name": "tune-filled", + "font_class": "tune-filled", + "unicode": "e6ca", + "unicode_decimal": 59082 + }, + { + "icon_id": "24932455", + "name": "a-rilidaka-filled", + "font_class": "calendar-filled", + "unicode": "e6c0", + "unicode_decimal": 59072 + }, + { + "icon_id": "24932456", + "name": "notification-filled", + "font_class": "notification-filled", + "unicode": "e6c1", + "unicode_decimal": 59073 + }, + { + "icon_id": "24932457", + "name": "wallet-filled", + "font_class": "wallet-filled", + "unicode": "e6c2", + "unicode_decimal": 59074 + }, + { + "icon_id": "24932458", + "name": "paihangbang-filled", + "font_class": "medal-filled", + "unicode": "e6c3", + "unicode_decimal": 59075 + }, + { + "icon_id": "24932459", + "name": "gift-filled", + "font_class": "gift-filled", + "unicode": "e6c4", + "unicode_decimal": 59076 + }, + { + "icon_id": "24932460", + "name": "fire-filled", + "font_class": "fire-filled", + "unicode": "e6c5", + "unicode_decimal": 59077 + }, + { + "icon_id": "24928001", + "name": "refreshempty", + "font_class": "refreshempty", + "unicode": "e6bf", + "unicode_decimal": 59071 + }, + { + "icon_id": "24926853", + "name": "location-ellipse", + "font_class": "location-filled", + "unicode": "e6af", + "unicode_decimal": 59055 + }, + { + "icon_id": "24926735", + "name": "person-filled", + "font_class": "person-filled", + "unicode": "e69d", + "unicode_decimal": 59037 + }, + { + "icon_id": "24926703", + "name": "personadd-filled", + "font_class": "personadd-filled", + "unicode": "e698", + "unicode_decimal": 59032 + }, + { + "icon_id": "24923351", + "name": "back", + "font_class": "back", + "unicode": "e6b9", + "unicode_decimal": 59065 + }, + { + "icon_id": "24923352", + "name": "forward", + "font_class": "forward", + "unicode": "e6ba", + "unicode_decimal": 59066 + }, + { + "icon_id": "24923353", + "name": "arrowthinright", + "font_class": "arrow-right", + "unicode": "e6bb", + "unicode_decimal": 59067 + }, + { + "icon_id": "24923353", + "name": "arrowthinright", + "font_class": "arrowthinright", + "unicode": "e6bb", + "unicode_decimal": 59067 + }, + { + "icon_id": "24923354", + "name": "arrowthinleft", + "font_class": "arrow-left", + "unicode": "e6bc", + "unicode_decimal": 59068 + }, + { + "icon_id": "24923354", + "name": "arrowthinleft", + "font_class": "arrowthinleft", + "unicode": "e6bc", + "unicode_decimal": 59068 + }, + { + "icon_id": "24923355", + "name": "arrowthinup", + "font_class": "arrow-up", + "unicode": "e6bd", + "unicode_decimal": 59069 + }, + { + "icon_id": "24923355", + "name": "arrowthinup", + "font_class": "arrowthinup", + "unicode": "e6bd", + "unicode_decimal": 59069 + }, + { + "icon_id": "24923356", + "name": "arrowthindown", + "font_class": "arrow-down", + "unicode": "e6be", + "unicode_decimal": 59070 + },{ + "icon_id": "24923356", + "name": "arrowthindown", + "font_class": "arrowthindown", + "unicode": "e6be", + "unicode_decimal": 59070 + }, + { + "icon_id": "24923349", + "name": "arrowdown", + "font_class": "bottom", + "unicode": "e6b8", + "unicode_decimal": 59064 + },{ + "icon_id": "24923349", + "name": "arrowdown", + "font_class": "arrowdown", + "unicode": "e6b8", + "unicode_decimal": 59064 + }, + { + "icon_id": "24923346", + "name": "arrowright", + "font_class": "right", + "unicode": "e6b5", + "unicode_decimal": 59061 + }, + { + "icon_id": "24923346", + "name": "arrowright", + "font_class": "arrowright", + "unicode": "e6b5", + "unicode_decimal": 59061 + }, + { + "icon_id": "24923347", + "name": "arrowup", + "font_class": "top", + "unicode": "e6b6", + "unicode_decimal": 59062 + }, + { + "icon_id": "24923347", + "name": "arrowup", + "font_class": "arrowup", + "unicode": "e6b6", + "unicode_decimal": 59062 + }, + { + "icon_id": "24923348", + "name": "arrowleft", + "font_class": "left", + "unicode": "e6b7", + "unicode_decimal": 59063 + }, + { + "icon_id": "24923348", + "name": "arrowleft", + "font_class": "arrowleft", + "unicode": "e6b7", + "unicode_decimal": 59063 + }, + { + "icon_id": "24923334", + "name": "eye", + "font_class": "eye", + "unicode": "e651", + "unicode_decimal": 58961 + }, + { + "icon_id": "24923335", + "name": "eye-filled", + "font_class": "eye-filled", + "unicode": "e66a", + "unicode_decimal": 58986 + }, + { + "icon_id": "24923336", + "name": "eye-slash", + "font_class": "eye-slash", + "unicode": "e6b3", + "unicode_decimal": 59059 + }, + { + "icon_id": "24923337", + "name": "eye-slash-filled", + "font_class": "eye-slash-filled", + "unicode": "e6b4", + "unicode_decimal": 59060 + }, + { + "icon_id": "24923305", + "name": "info-filled", + "font_class": "info-filled", + "unicode": "e649", + "unicode_decimal": 58953 + }, + { + "icon_id": "24923299", + "name": "reload-01", + "font_class": "reload", + "unicode": "e6b2", + "unicode_decimal": 59058 + }, + { + "icon_id": "24923195", + "name": "mic_slash_fill", + "font_class": "micoff-filled", + "unicode": "e6b0", + "unicode_decimal": 59056 + }, + { + "icon_id": "24923165", + "name": "map-pin-ellipse", + "font_class": "map-pin-ellipse", + "unicode": "e6ac", + "unicode_decimal": 59052 + }, + { + "icon_id": "24923166", + "name": "map-pin", + "font_class": "map-pin", + "unicode": "e6ad", + "unicode_decimal": 59053 + }, + { + "icon_id": "24923167", + "name": "location", + "font_class": "location", + "unicode": "e6ae", + "unicode_decimal": 59054 + }, + { + "icon_id": "24923064", + "name": "starhalf", + "font_class": "starhalf", + "unicode": "e683", + "unicode_decimal": 59011 + }, + { + "icon_id": "24923065", + "name": "star", + "font_class": "star", + "unicode": "e688", + "unicode_decimal": 59016 + }, + { + "icon_id": "24923066", + "name": "star-filled", + "font_class": "star-filled", + "unicode": "e68f", + "unicode_decimal": 59023 + }, + { + "icon_id": "24899646", + "name": "a-rilidaka", + "font_class": "calendar", + "unicode": "e6a0", + "unicode_decimal": 59040 + }, + { + "icon_id": "24899647", + "name": "fire", + "font_class": "fire", + "unicode": "e6a1", + "unicode_decimal": 59041 + }, + { + "icon_id": "24899648", + "name": "paihangbang", + "font_class": "medal", + "unicode": "e6a2", + "unicode_decimal": 59042 + }, + { + "icon_id": "24899649", + "name": "font", + "font_class": "font", + "unicode": "e6a3", + "unicode_decimal": 59043 + }, + { + "icon_id": "24899650", + "name": "gift", + "font_class": "gift", + "unicode": "e6a4", + "unicode_decimal": 59044 + }, + { + "icon_id": "24899651", + "name": "link", + "font_class": "link", + "unicode": "e6a5", + "unicode_decimal": 59045 + }, + { + "icon_id": "24899652", + "name": "notification", + "font_class": "notification", + "unicode": "e6a6", + "unicode_decimal": 59046 + }, + { + "icon_id": "24899653", + "name": "staff", + "font_class": "staff", + "unicode": "e6a7", + "unicode_decimal": 59047 + }, + { + "icon_id": "24899654", + "name": "VIP", + "font_class": "vip", + "unicode": "e6a8", + "unicode_decimal": 59048 + }, + { + "icon_id": "24899655", + "name": "folder_add", + "font_class": "folder-add", + "unicode": "e6a9", + "unicode_decimal": 59049 + }, + { + "icon_id": "24899656", + "name": "tune", + "font_class": "tune", + "unicode": "e6aa", + "unicode_decimal": 59050 + }, + { + "icon_id": "24899657", + "name": "shimingrenzheng", + "font_class": "auth", + "unicode": "e6ab", + "unicode_decimal": 59051 + }, + { + "icon_id": "24899565", + "name": "person", + "font_class": "person", + "unicode": "e699", + "unicode_decimal": 59033 + }, + { + "icon_id": "24899566", + "name": "email-filled", + "font_class": "email-filled", + "unicode": "e69a", + "unicode_decimal": 59034 + }, + { + "icon_id": "24899567", + "name": "phone-filled", + "font_class": "phone-filled", + "unicode": "e69b", + "unicode_decimal": 59035 + }, + { + "icon_id": "24899568", + "name": "phone", + "font_class": "phone", + "unicode": "e69c", + "unicode_decimal": 59036 + }, + { + "icon_id": "24899570", + "name": "email", + "font_class": "email", + "unicode": "e69e", + "unicode_decimal": 59038 + }, + { + "icon_id": "24899571", + "name": "personadd", + "font_class": "personadd", + "unicode": "e69f", + "unicode_decimal": 59039 + }, + { + "icon_id": "24899558", + "name": "chatboxes-filled", + "font_class": "chatboxes-filled", + "unicode": "e692", + "unicode_decimal": 59026 + }, + { + "icon_id": "24899559", + "name": "contact", + "font_class": "contact", + "unicode": "e693", + "unicode_decimal": 59027 + }, + { + "icon_id": "24899560", + "name": "chatbubble-filled", + "font_class": "chatbubble-filled", + "unicode": "e694", + "unicode_decimal": 59028 + }, + { + "icon_id": "24899561", + "name": "contact-filled", + "font_class": "contact-filled", + "unicode": "e695", + "unicode_decimal": 59029 + }, + { + "icon_id": "24899562", + "name": "chatboxes", + "font_class": "chatboxes", + "unicode": "e696", + "unicode_decimal": 59030 + }, + { + "icon_id": "24899563", + "name": "chatbubble", + "font_class": "chatbubble", + "unicode": "e697", + "unicode_decimal": 59031 + }, + { + "icon_id": "24881290", + "name": "upload-filled", + "font_class": "upload-filled", + "unicode": "e68e", + "unicode_decimal": 59022 + }, + { + "icon_id": "24881292", + "name": "upload", + "font_class": "upload", + "unicode": "e690", + "unicode_decimal": 59024 + }, + { + "icon_id": "24881293", + "name": "weixin", + "font_class": "weixin", + "unicode": "e691", + "unicode_decimal": 59025 + }, + { + "icon_id": "24881274", + "name": "compose", + "font_class": "compose", + "unicode": "e67f", + "unicode_decimal": 59007 + }, + { + "icon_id": "24881275", + "name": "qq", + "font_class": "qq", + "unicode": "e680", + "unicode_decimal": 59008 + }, + { + "icon_id": "24881276", + "name": "download-filled", + "font_class": "download-filled", + "unicode": "e681", + "unicode_decimal": 59009 + }, + { + "icon_id": "24881277", + "name": "pengyouquan", + "font_class": "pyq", + "unicode": "e682", + "unicode_decimal": 59010 + }, + { + "icon_id": "24881279", + "name": "sound", + "font_class": "sound", + "unicode": "e684", + "unicode_decimal": 59012 + }, + { + "icon_id": "24881280", + "name": "trash-filled", + "font_class": "trash-filled", + "unicode": "e685", + "unicode_decimal": 59013 + }, + { + "icon_id": "24881281", + "name": "sound-filled", + "font_class": "sound-filled", + "unicode": "e686", + "unicode_decimal": 59014 + }, + { + "icon_id": "24881282", + "name": "trash", + "font_class": "trash", + "unicode": "e687", + "unicode_decimal": 59015 + }, + { + "icon_id": "24881284", + "name": "videocam-filled", + "font_class": "videocam-filled", + "unicode": "e689", + "unicode_decimal": 59017 + }, + { + "icon_id": "24881285", + "name": "spinner-cycle", + "font_class": "spinner-cycle", + "unicode": "e68a", + "unicode_decimal": 59018 + }, + { + "icon_id": "24881286", + "name": "weibo", + "font_class": "weibo", + "unicode": "e68b", + "unicode_decimal": 59019 + }, + { + "icon_id": "24881288", + "name": "videocam", + "font_class": "videocam", + "unicode": "e68c", + "unicode_decimal": 59020 + }, + { + "icon_id": "24881289", + "name": "download", + "font_class": "download", + "unicode": "e68d", + "unicode_decimal": 59021 + }, + { + "icon_id": "24879601", + "name": "help", + "font_class": "help", + "unicode": "e679", + "unicode_decimal": 59001 + }, + { + "icon_id": "24879602", + "name": "navigate-filled", + "font_class": "navigate-filled", + "unicode": "e67a", + "unicode_decimal": 59002 + }, + { + "icon_id": "24879603", + "name": "plusempty", + "font_class": "plusempty", + "unicode": "e67b", + "unicode_decimal": 59003 + }, + { + "icon_id": "24879604", + "name": "smallcircle", + "font_class": "smallcircle", + "unicode": "e67c", + "unicode_decimal": 59004 + }, + { + "icon_id": "24879605", + "name": "minus-filled", + "font_class": "minus-filled", + "unicode": "e67d", + "unicode_decimal": 59005 + }, + { + "icon_id": "24879606", + "name": "micoff", + "font_class": "micoff", + "unicode": "e67e", + "unicode_decimal": 59006 + }, + { + "icon_id": "24879588", + "name": "closeempty", + "font_class": "closeempty", + "unicode": "e66c", + "unicode_decimal": 58988 + }, + { + "icon_id": "24879589", + "name": "clear", + "font_class": "clear", + "unicode": "e66d", + "unicode_decimal": 58989 + }, + { + "icon_id": "24879590", + "name": "navigate", + "font_class": "navigate", + "unicode": "e66e", + "unicode_decimal": 58990 + }, + { + "icon_id": "24879591", + "name": "minus", + "font_class": "minus", + "unicode": "e66f", + "unicode_decimal": 58991 + }, + { + "icon_id": "24879592", + "name": "image", + "font_class": "image", + "unicode": "e670", + "unicode_decimal": 58992 + }, + { + "icon_id": "24879593", + "name": "mic", + "font_class": "mic", + "unicode": "e671", + "unicode_decimal": 58993 + }, + { + "icon_id": "24879594", + "name": "paperplane", + "font_class": "paperplane", + "unicode": "e672", + "unicode_decimal": 58994 + }, + { + "icon_id": "24879595", + "name": "close", + "font_class": "close", + "unicode": "e673", + "unicode_decimal": 58995 + }, + { + "icon_id": "24879596", + "name": "help-filled", + "font_class": "help-filled", + "unicode": "e674", + "unicode_decimal": 58996 + }, + { + "icon_id": "24879597", + "name": "plus-filled", + "font_class": "paperplane-filled", + "unicode": "e675", + "unicode_decimal": 58997 + }, + { + "icon_id": "24879598", + "name": "plus", + "font_class": "plus", + "unicode": "e676", + "unicode_decimal": 58998 + }, + { + "icon_id": "24879599", + "name": "mic-filled", + "font_class": "mic-filled", + "unicode": "e677", + "unicode_decimal": 58999 + }, + { + "icon_id": "24879600", + "name": "image-filled", + "font_class": "image-filled", + "unicode": "e678", + "unicode_decimal": 59000 + }, + { + "icon_id": "24855900", + "name": "locked-filled", + "font_class": "locked-filled", + "unicode": "e668", + "unicode_decimal": 58984 + }, + { + "icon_id": "24855901", + "name": "info", + "font_class": "info", + "unicode": "e669", + "unicode_decimal": 58985 + }, + { + "icon_id": "24855903", + "name": "locked", + "font_class": "locked", + "unicode": "e66b", + "unicode_decimal": 58987 + }, + { + "icon_id": "24855884", + "name": "camera-filled", + "font_class": "camera-filled", + "unicode": "e658", + "unicode_decimal": 58968 + }, + { + "icon_id": "24855885", + "name": "chat-filled", + "font_class": "chat-filled", + "unicode": "e659", + "unicode_decimal": 58969 + }, + { + "icon_id": "24855886", + "name": "camera", + "font_class": "camera", + "unicode": "e65a", + "unicode_decimal": 58970 + }, + { + "icon_id": "24855887", + "name": "circle", + "font_class": "circle", + "unicode": "e65b", + "unicode_decimal": 58971 + }, + { + "icon_id": "24855888", + "name": "checkmarkempty", + "font_class": "checkmarkempty", + "unicode": "e65c", + "unicode_decimal": 58972 + }, + { + "icon_id": "24855889", + "name": "chat", + "font_class": "chat", + "unicode": "e65d", + "unicode_decimal": 58973 + }, + { + "icon_id": "24855890", + "name": "circle-filled", + "font_class": "circle-filled", + "unicode": "e65e", + "unicode_decimal": 58974 + }, + { + "icon_id": "24855891", + "name": "flag", + "font_class": "flag", + "unicode": "e65f", + "unicode_decimal": 58975 + }, + { + "icon_id": "24855892", + "name": "flag-filled", + "font_class": "flag-filled", + "unicode": "e660", + "unicode_decimal": 58976 + }, + { + "icon_id": "24855893", + "name": "gear-filled", + "font_class": "gear-filled", + "unicode": "e661", + "unicode_decimal": 58977 + }, + { + "icon_id": "24855894", + "name": "home", + "font_class": "home", + "unicode": "e662", + "unicode_decimal": 58978 + }, + { + "icon_id": "24855895", + "name": "home-filled", + "font_class": "home-filled", + "unicode": "e663", + "unicode_decimal": 58979 + }, + { + "icon_id": "24855896", + "name": "gear", + "font_class": "gear", + "unicode": "e664", + "unicode_decimal": 58980 + }, + { + "icon_id": "24855897", + "name": "smallcircle-filled", + "font_class": "smallcircle-filled", + "unicode": "e665", + "unicode_decimal": 58981 + }, + { + "icon_id": "24855898", + "name": "map-filled", + "font_class": "map-filled", + "unicode": "e666", + "unicode_decimal": 58982 + }, + { + "icon_id": "24855899", + "name": "map", + "font_class": "map", + "unicode": "e667", + "unicode_decimal": 58983 + }, + { + "icon_id": "24855825", + "name": "refresh-filled", + "font_class": "refresh-filled", + "unicode": "e656", + "unicode_decimal": 58966 + }, + { + "icon_id": "24855826", + "name": "refresh", + "font_class": "refresh", + "unicode": "e657", + "unicode_decimal": 58967 + }, + { + "icon_id": "24855808", + "name": "cloud-upload", + "font_class": "cloud-upload", + "unicode": "e645", + "unicode_decimal": 58949 + }, + { + "icon_id": "24855809", + "name": "cloud-download-filled", + "font_class": "cloud-download-filled", + "unicode": "e646", + "unicode_decimal": 58950 + }, + { + "icon_id": "24855810", + "name": "cloud-download", + "font_class": "cloud-download", + "unicode": "e647", + "unicode_decimal": 58951 + }, + { + "icon_id": "24855811", + "name": "cloud-upload-filled", + "font_class": "cloud-upload-filled", + "unicode": "e648", + "unicode_decimal": 58952 + }, + { + "icon_id": "24855813", + "name": "redo", + "font_class": "redo", + "unicode": "e64a", + "unicode_decimal": 58954 + }, + { + "icon_id": "24855814", + "name": "images-filled", + "font_class": "images-filled", + "unicode": "e64b", + "unicode_decimal": 58955 + }, + { + "icon_id": "24855815", + "name": "undo-filled", + "font_class": "undo-filled", + "unicode": "e64c", + "unicode_decimal": 58956 + }, + { + "icon_id": "24855816", + "name": "more", + "font_class": "more", + "unicode": "e64d", + "unicode_decimal": 58957 + }, + { + "icon_id": "24855817", + "name": "more-filled", + "font_class": "more-filled", + "unicode": "e64e", + "unicode_decimal": 58958 + }, + { + "icon_id": "24855818", + "name": "undo", + "font_class": "undo", + "unicode": "e64f", + "unicode_decimal": 58959 + }, + { + "icon_id": "24855819", + "name": "images", + "font_class": "images", + "unicode": "e650", + "unicode_decimal": 58960 + }, + { + "icon_id": "24855821", + "name": "paperclip", + "font_class": "paperclip", + "unicode": "e652", + "unicode_decimal": 58962 + }, + { + "icon_id": "24855822", + "name": "settings", + "font_class": "settings", + "unicode": "e653", + "unicode_decimal": 58963 + }, + { + "icon_id": "24855823", + "name": "search", + "font_class": "search", + "unicode": "e654", + "unicode_decimal": 58964 + }, + { + "icon_id": "24855824", + "name": "redo-filled", + "font_class": "redo-filled", + "unicode": "e655", + "unicode_decimal": 58965 + }, + { + "icon_id": "24841702", + "name": "list", + "font_class": "list", + "unicode": "e644", + "unicode_decimal": 58948 + }, + { + "icon_id": "24841489", + "name": "mail-open-filled", + "font_class": "mail-open-filled", + "unicode": "e63a", + "unicode_decimal": 58938 + }, + { + "icon_id": "24841491", + "name": "hand-thumbsdown-filled", + "font_class": "hand-down-filled", + "unicode": "e63c", + "unicode_decimal": 58940 + }, + { + "icon_id": "24841492", + "name": "hand-thumbsdown", + "font_class": "hand-down", + "unicode": "e63d", + "unicode_decimal": 58941 + }, + { + "icon_id": "24841493", + "name": "hand-thumbsup-filled", + "font_class": "hand-up-filled", + "unicode": "e63e", + "unicode_decimal": 58942 + }, + { + "icon_id": "24841494", + "name": "hand-thumbsup", + "font_class": "hand-up", + "unicode": "e63f", + "unicode_decimal": 58943 + }, + { + "icon_id": "24841496", + "name": "heart-filled", + "font_class": "heart-filled", + "unicode": "e641", + "unicode_decimal": 58945 + }, + { + "icon_id": "24841498", + "name": "mail-open", + "font_class": "mail-open", + "unicode": "e643", + "unicode_decimal": 58947 + }, + { + "icon_id": "24841488", + "name": "heart", + "font_class": "heart", + "unicode": "e639", + "unicode_decimal": 58937 + }, + { + "icon_id": "24839963", + "name": "loop", + "font_class": "loop", + "unicode": "e633", + "unicode_decimal": 58931 + }, + { + "icon_id": "24839866", + "name": "pulldown", + "font_class": "pulldown", + "unicode": "e632", + "unicode_decimal": 58930 + }, + { + "icon_id": "24813798", + "name": "scan", + "font_class": "scan", + "unicode": "e62a", + "unicode_decimal": 58922 + }, + { + "icon_id": "24813786", + "name": "bars", + "font_class": "bars", + "unicode": "e627", + "unicode_decimal": 58919 + }, + { + "icon_id": "24813788", + "name": "cart-filled", + "font_class": "cart-filled", + "unicode": "e629", + "unicode_decimal": 58921 + }, + { + "icon_id": "24813790", + "name": "checkbox", + "font_class": "checkbox", + "unicode": "e62b", + "unicode_decimal": 58923 + }, + { + "icon_id": "24813791", + "name": "checkbox-filled", + "font_class": "checkbox-filled", + "unicode": "e62c", + "unicode_decimal": 58924 + }, + { + "icon_id": "24813794", + "name": "shop", + "font_class": "shop", + "unicode": "e62f", + "unicode_decimal": 58927 + }, + { + "icon_id": "24813795", + "name": "headphones", + "font_class": "headphones", + "unicode": "e630", + "unicode_decimal": 58928 + }, + { + "icon_id": "24813796", + "name": "cart", + "font_class": "cart", + "unicode": "e631", + "unicode_decimal": 58929 + } + ] +} diff --git a/uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.vue b/uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.vue new file mode 100644 index 0000000..86e7444 --- /dev/null +++ b/uniapp/uni_modules/uni-icons/components/uni-icons/uni-icons.vue @@ -0,0 +1,96 @@ + + + + + diff --git a/uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.css b/uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.css new file mode 100644 index 0000000..2f56eab --- /dev/null +++ b/uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.css @@ -0,0 +1,663 @@ +.uniui-color:before { + content: "\e6cf"; +} + +.uniui-wallet:before { + content: "\e6b1"; +} + +.uniui-settings-filled:before { + content: "\e6ce"; +} + +.uniui-auth-filled:before { + content: "\e6cc"; +} + +.uniui-shop-filled:before { + content: "\e6cd"; +} + +.uniui-staff-filled:before { + content: "\e6cb"; +} + +.uniui-vip-filled:before { + content: "\e6c6"; +} + +.uniui-plus-filled:before { + content: "\e6c7"; +} + +.uniui-folder-add-filled:before { + content: "\e6c8"; +} + +.uniui-color-filled:before { + content: "\e6c9"; +} + +.uniui-tune-filled:before { + content: "\e6ca"; +} + +.uniui-calendar-filled:before { + content: "\e6c0"; +} + +.uniui-notification-filled:before { + content: "\e6c1"; +} + +.uniui-wallet-filled:before { + content: "\e6c2"; +} + +.uniui-medal-filled:before { + content: "\e6c3"; +} + +.uniui-gift-filled:before { + content: "\e6c4"; +} + +.uniui-fire-filled:before { + content: "\e6c5"; +} + +.uniui-refreshempty:before { + content: "\e6bf"; +} + +.uniui-location-filled:before { + content: "\e6af"; +} + +.uniui-person-filled:before { + content: "\e69d"; +} + +.uniui-personadd-filled:before { + content: "\e698"; +} + +.uniui-back:before { + content: "\e6b9"; +} + +.uniui-forward:before { + content: "\e6ba"; +} + +.uniui-arrow-right:before { + content: "\e6bb"; +} + +.uniui-arrowthinright:before { + content: "\e6bb"; +} + +.uniui-arrow-left:before { + content: "\e6bc"; +} + +.uniui-arrowthinleft:before { + content: "\e6bc"; +} + +.uniui-arrow-up:before { + content: "\e6bd"; +} + +.uniui-arrowthinup:before { + content: "\e6bd"; +} + +.uniui-arrow-down:before { + content: "\e6be"; +} + +.uniui-arrowthindown:before { + content: "\e6be"; +} + +.uniui-bottom:before { + content: "\e6b8"; +} + +.uniui-arrowdown:before { + content: "\e6b8"; +} + +.uniui-right:before { + content: "\e6b5"; +} + +.uniui-arrowright:before { + content: "\e6b5"; +} + +.uniui-top:before { + content: "\e6b6"; +} + +.uniui-arrowup:before { + content: "\e6b6"; +} + +.uniui-left:before { + content: "\e6b7"; +} + +.uniui-arrowleft:before { + content: "\e6b7"; +} + +.uniui-eye:before { + content: "\e651"; +} + +.uniui-eye-filled:before { + content: "\e66a"; +} + +.uniui-eye-slash:before { + content: "\e6b3"; +} + +.uniui-eye-slash-filled:before { + content: "\e6b4"; +} + +.uniui-info-filled:before { + content: "\e649"; +} + +.uniui-reload:before { + content: "\e6b2"; +} + +.uniui-micoff-filled:before { + content: "\e6b0"; +} + +.uniui-map-pin-ellipse:before { + content: "\e6ac"; +} + +.uniui-map-pin:before { + content: "\e6ad"; +} + +.uniui-location:before { + content: "\e6ae"; +} + +.uniui-starhalf:before { + content: "\e683"; +} + +.uniui-star:before { + content: "\e688"; +} + +.uniui-star-filled:before { + content: "\e68f"; +} + +.uniui-calendar:before { + content: "\e6a0"; +} + +.uniui-fire:before { + content: "\e6a1"; +} + +.uniui-medal:before { + content: "\e6a2"; +} + +.uniui-font:before { + content: "\e6a3"; +} + +.uniui-gift:before { + content: "\e6a4"; +} + +.uniui-link:before { + content: "\e6a5"; +} + +.uniui-notification:before { + content: "\e6a6"; +} + +.uniui-staff:before { + content: "\e6a7"; +} + +.uniui-vip:before { + content: "\e6a8"; +} + +.uniui-folder-add:before { + content: "\e6a9"; +} + +.uniui-tune:before { + content: "\e6aa"; +} + +.uniui-auth:before { + content: "\e6ab"; +} + +.uniui-person:before { + content: "\e699"; +} + +.uniui-email-filled:before { + content: "\e69a"; +} + +.uniui-phone-filled:before { + content: "\e69b"; +} + +.uniui-phone:before { + content: "\e69c"; +} + +.uniui-email:before { + content: "\e69e"; +} + +.uniui-personadd:before { + content: "\e69f"; +} + +.uniui-chatboxes-filled:before { + content: "\e692"; +} + +.uniui-contact:before { + content: "\e693"; +} + +.uniui-chatbubble-filled:before { + content: "\e694"; +} + +.uniui-contact-filled:before { + content: "\e695"; +} + +.uniui-chatboxes:before { + content: "\e696"; +} + +.uniui-chatbubble:before { + content: "\e697"; +} + +.uniui-upload-filled:before { + content: "\e68e"; +} + +.uniui-upload:before { + content: "\e690"; +} + +.uniui-weixin:before { + content: "\e691"; +} + +.uniui-compose:before { + content: "\e67f"; +} + +.uniui-qq:before { + content: "\e680"; +} + +.uniui-download-filled:before { + content: "\e681"; +} + +.uniui-pyq:before { + content: "\e682"; +} + +.uniui-sound:before { + content: "\e684"; +} + +.uniui-trash-filled:before { + content: "\e685"; +} + +.uniui-sound-filled:before { + content: "\e686"; +} + +.uniui-trash:before { + content: "\e687"; +} + +.uniui-videocam-filled:before { + content: "\e689"; +} + +.uniui-spinner-cycle:before { + content: "\e68a"; +} + +.uniui-weibo:before { + content: "\e68b"; +} + +.uniui-videocam:before { + content: "\e68c"; +} + +.uniui-download:before { + content: "\e68d"; +} + +.uniui-help:before { + content: "\e679"; +} + +.uniui-navigate-filled:before { + content: "\e67a"; +} + +.uniui-plusempty:before { + content: "\e67b"; +} + +.uniui-smallcircle:before { + content: "\e67c"; +} + +.uniui-minus-filled:before { + content: "\e67d"; +} + +.uniui-micoff:before { + content: "\e67e"; +} + +.uniui-closeempty:before { + content: "\e66c"; +} + +.uniui-clear:before { + content: "\e66d"; +} + +.uniui-navigate:before { + content: "\e66e"; +} + +.uniui-minus:before { + content: "\e66f"; +} + +.uniui-image:before { + content: "\e670"; +} + +.uniui-mic:before { + content: "\e671"; +} + +.uniui-paperplane:before { + content: "\e672"; +} + +.uniui-close:before { + content: "\e673"; +} + +.uniui-help-filled:before { + content: "\e674"; +} + +.uniui-paperplane-filled:before { + content: "\e675"; +} + +.uniui-plus:before { + content: "\e676"; +} + +.uniui-mic-filled:before { + content: "\e677"; +} + +.uniui-image-filled:before { + content: "\e678"; +} + +.uniui-locked-filled:before { + content: "\e668"; +} + +.uniui-info:before { + content: "\e669"; +} + +.uniui-locked:before { + content: "\e66b"; +} + +.uniui-camera-filled:before { + content: "\e658"; +} + +.uniui-chat-filled:before { + content: "\e659"; +} + +.uniui-camera:before { + content: "\e65a"; +} + +.uniui-circle:before { + content: "\e65b"; +} + +.uniui-checkmarkempty:before { + content: "\e65c"; +} + +.uniui-chat:before { + content: "\e65d"; +} + +.uniui-circle-filled:before { + content: "\e65e"; +} + +.uniui-flag:before { + content: "\e65f"; +} + +.uniui-flag-filled:before { + content: "\e660"; +} + +.uniui-gear-filled:before { + content: "\e661"; +} + +.uniui-home:before { + content: "\e662"; +} + +.uniui-home-filled:before { + content: "\e663"; +} + +.uniui-gear:before { + content: "\e664"; +} + +.uniui-smallcircle-filled:before { + content: "\e665"; +} + +.uniui-map-filled:before { + content: "\e666"; +} + +.uniui-map:before { + content: "\e667"; +} + +.uniui-refresh-filled:before { + content: "\e656"; +} + +.uniui-refresh:before { + content: "\e657"; +} + +.uniui-cloud-upload:before { + content: "\e645"; +} + +.uniui-cloud-download-filled:before { + content: "\e646"; +} + +.uniui-cloud-download:before { + content: "\e647"; +} + +.uniui-cloud-upload-filled:before { + content: "\e648"; +} + +.uniui-redo:before { + content: "\e64a"; +} + +.uniui-images-filled:before { + content: "\e64b"; +} + +.uniui-undo-filled:before { + content: "\e64c"; +} + +.uniui-more:before { + content: "\e64d"; +} + +.uniui-more-filled:before { + content: "\e64e"; +} + +.uniui-undo:before { + content: "\e64f"; +} + +.uniui-images:before { + content: "\e650"; +} + +.uniui-paperclip:before { + content: "\e652"; +} + +.uniui-settings:before { + content: "\e653"; +} + +.uniui-search:before { + content: "\e654"; +} + +.uniui-redo-filled:before { + content: "\e655"; +} + +.uniui-list:before { + content: "\e644"; +} + +.uniui-mail-open-filled:before { + content: "\e63a"; +} + +.uniui-hand-down-filled:before { + content: "\e63c"; +} + +.uniui-hand-down:before { + content: "\e63d"; +} + +.uniui-hand-up-filled:before { + content: "\e63e"; +} + +.uniui-hand-up:before { + content: "\e63f"; +} + +.uniui-heart-filled:before { + content: "\e641"; +} + +.uniui-mail-open:before { + content: "\e643"; +} + +.uniui-heart:before { + content: "\e639"; +} + +.uniui-loop:before { + content: "\e633"; +} + +.uniui-pulldown:before { + content: "\e632"; +} + +.uniui-scan:before { + content: "\e62a"; +} + +.uniui-bars:before { + content: "\e627"; +} + +.uniui-cart-filled:before { + content: "\e629"; +} + +.uniui-checkbox:before { + content: "\e62b"; +} + +.uniui-checkbox-filled:before { + content: "\e62c"; +} + +.uniui-shop:before { + content: "\e62f"; +} + +.uniui-headphones:before { + content: "\e630"; +} + +.uniui-cart:before { + content: "\e631"; +} diff --git a/uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.ttf b/uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.ttf new file mode 100644 index 0000000..835f33b Binary files /dev/null and b/uniapp/uni_modules/uni-icons/components/uni-icons/uniicons.ttf differ diff --git a/uniapp/uni_modules/uni-icons/package.json b/uniapp/uni_modules/uni-icons/package.json new file mode 100644 index 0000000..d1c4e77 --- /dev/null +++ b/uniapp/uni_modules/uni-icons/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-icons", + "displayName": "uni-icons 图标", + "version": "1.3.5", + "description": "图标组件,用于展示移动端常见的图标,可自定义颜色、大小。", + "keywords": [ + "uni-ui", + "uniui", + "icon", + "图标" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.2.14" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-icons/readme.md b/uniapp/uni_modules/uni-icons/readme.md new file mode 100644 index 0000000..86234ba --- /dev/null +++ b/uniapp/uni_modules/uni-icons/readme.md @@ -0,0 +1,8 @@ +## Icons 图标 +> **组件名:uni-icons** +> 代码块: `uIcons` + +用于展示 icons 图标 。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-icons) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/uniapp/uni_modules/uni-indexed-list/changelog.md b/uniapp/uni_modules/uni-indexed-list/changelog.md new file mode 100644 index 0000000..08fa71c --- /dev/null +++ b/uniapp/uni_modules/uni-indexed-list/changelog.md @@ -0,0 +1,17 @@ +## 1.2.1(2021-11-22) +- 修复 vue3中某些scss变量无法找到的问题 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-indexed-list](https://uniapp.dcloud.io/component/uniui/uni-indexed-list) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.11(2021-05-12) +- 新增 组件示例地址 +## 1.0.10(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.9(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.8(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持 PC 端 diff --git a/uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list-item.vue b/uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list-item.vue new file mode 100644 index 0000000..2f13bae --- /dev/null +++ b/uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list-item.vue @@ -0,0 +1,144 @@ + + + + + diff --git a/uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list.vue b/uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list.vue new file mode 100644 index 0000000..35e168c --- /dev/null +++ b/uniapp/uni_modules/uni-indexed-list/components/uni-indexed-list/uni-indexed-list.vue @@ -0,0 +1,367 @@ + + + diff --git a/uniapp/uni_modules/uni-indexed-list/package.json b/uniapp/uni_modules/uni-indexed-list/package.json new file mode 100644 index 0000000..125c0e7 --- /dev/null +++ b/uniapp/uni_modules/uni-indexed-list/package.json @@ -0,0 +1,89 @@ +{ + "id": "uni-indexed-list", + "displayName": "uni-indexed-list 索引列表", + "version": "1.2.1", + "description": "索引列表组件,右侧带索引的列表,方便快速定位到具体内容,通常用于城市/机场选择等场景", + "keywords": [ + "uni-ui", + "索引列表", + "索引", + "列表" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uniapp/uni_modules/uni-indexed-list/readme.md b/uniapp/uni_modules/uni-indexed-list/readme.md new file mode 100644 index 0000000..44ad84b --- /dev/null +++ b/uniapp/uni_modules/uni-indexed-list/readme.md @@ -0,0 +1,11 @@ + + +## IndexedList 索引列表 +> **组件名:uni-indexed-list** +> 代码块: `uIndexedList` + + +用于展示索引列表。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-indexed-list) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/uniapp/uni_modules/uni-link/changelog.md b/uniapp/uni_modules/uni-link/changelog.md new file mode 100644 index 0000000..2cfbf59 --- /dev/null +++ b/uniapp/uni_modules/uni-link/changelog.md @@ -0,0 +1,17 @@ +## 1.0.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-link](https://uniapp.dcloud.io/component/uniui/uni-link) +## 1.1.7(2021-11-08) +## 0.0.7(2021-09-03) +- 修复 在 nvue 下不显示的 bug +## 0.0.6(2021-07-30) +- 新增 支持自定义插槽 +## 0.0.5(2021-06-21) +- 新增 download 属性,H5平台下载文件名 +## 0.0.4(2021-05-12) +- 新增 组件示例地址 +## 0.0.3(2021-03-09) +- 新增 href 属性支持 tel:|mailto: + +## 0.0.2(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-link/components/uni-link/uni-link.vue b/uniapp/uni_modules/uni-link/components/uni-link/uni-link.vue new file mode 100644 index 0000000..27c5468 --- /dev/null +++ b/uniapp/uni_modules/uni-link/components/uni-link/uni-link.vue @@ -0,0 +1,128 @@ + + + + + diff --git a/uniapp/uni_modules/uni-link/package.json b/uniapp/uni_modules/uni-link/package.json new file mode 100644 index 0000000..77b1986 --- /dev/null +++ b/uniapp/uni_modules/uni-link/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-link", + "displayName": "uni-link 超链接", + "version": "1.0.0", + "description": "uni-link是一个外部网页超链接组件,在小程序内复制url,在app内打开外部浏览器,在h5端打", + "keywords": [ + "uni-ui", + "uniui", + "link", + "超链接", + "" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "y", + "联盟": "y" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-link/readme.md b/uniapp/uni_modules/uni-link/readme.md new file mode 100644 index 0000000..7f09e94 --- /dev/null +++ b/uniapp/uni_modules/uni-link/readme.md @@ -0,0 +1,11 @@ + + +## Link 链接 +> **组件名:uni-link** +> 代码块: `uLink` + + +uni-link是一个外部网页超链接组件,在小程序内复制url,在app内打开外部浏览器,在h5端打开新网页。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-link) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-list/changelog.md b/uniapp/uni_modules/uni-list/changelog.md new file mode 100644 index 0000000..8254a18 --- /dev/null +++ b/uniapp/uni_modules/uni-list/changelog.md @@ -0,0 +1,46 @@ +## 1.2.14(2023-04-14) +- 优化 uni-list-chat 具名插槽`header` 非app端套一层元素,方便使用时通过外层元素定位实现样式修改 +## 1.2.13(2023-03-03) +- uni-list-chat 新增 支持具名插槽`header` +## 1.2.12(2023-02-01) +- 新增 列表图标新增 customPrefix 属性 ,用法 [详见](https://uniapp.dcloud.net.cn/component/uniui/uni-icons.html#icons-props) +## 1.2.11(2023-01-31) +- 修复 无反馈效果呈现的bug +## 1.2.9(2022-11-22) +- 修复 uni-list-chat 在vue3下跳转报错的bug +## 1.2.8(2022-11-21) +- 修复 uni-list-chat avatar属性 值为本地路径时错误的问题 +## 1.2.7(2022-11-21) +- 修复 uni-list-chat avatar属性 在腾讯云版uniCloud下错误的问题 +## 1.2.6(2022-11-18) +- 修复 uni-list-chat note属性 支持:“草稿”字样功能 文本少1位的问题 +## 1.2.5(2022-11-15) +- 修复 uni-list-item 的 customStyle 属性 padding值在 H5端 无效的bug +## 1.2.4(2022-11-15) +- 修复 uni-list-item 的 customStyle 属性 padding值在nvue(vue2)下无效的bug +## 1.2.3(2022-11-14) +- uni-list-chat 新增 avatar 支持 fileId +## 1.2.2(2022-11-11) +- uni-list 新增属性 render-reverse 详情参考:[https://uniapp.dcloud.net.cn/component/list.html](https://uniapp.dcloud.net.cn/component/list.html) +- uni-list-chat note属性 支持:“草稿”字样 加红显示 详情参考uni-im:[https://ext.dcloud.net.cn/plugin?name=uni-im](https://ext.dcloud.net.cn/plugin?name=uni-im) +- uni-list-item 新增属性 customStyle 支持设置padding、backgroundColor +## 1.2.1(2022-03-30) +- 删除无用文件 +## 1.2.0(2021-11-23) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-list](https://uniapp.dcloud.io/component/uniui/uni-list) +## 1.1.3(2021-08-30) +- 修复 在vue3中to属性在发行应用的时候报错的bug +## 1.1.2(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.1.1(2021-07-21) +- 修复 与其他组件嵌套使用时,点击失效的Bug +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.17(2021-05-12) +- 新增 组件示例地址 +## 1.0.16(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.0.15(2021-02-05) +- 调整为uni_modules目录规范 +- 修复 uni-list-chat 角标显示不正常的问题 diff --git a/uniapp/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue b/uniapp/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue new file mode 100644 index 0000000..b9349c2 --- /dev/null +++ b/uniapp/uni_modules/uni-list/components/uni-list-ad/uni-list-ad.vue @@ -0,0 +1,107 @@ + + + + + diff --git a/uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss b/uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss new file mode 100644 index 0000000..311f8d9 --- /dev/null +++ b/uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.scss @@ -0,0 +1,58 @@ +/** + * 这里是 uni-list 组件内置的常用样式变量 + * 如果需要覆盖样式,这里提供了基本的组件样式变量,您可以尝试修改这里的变量,去完成样式替换,而不用去修改源码 + * + */ + +// 背景色 +$background-color : #fff; +// 分割线颜色 +$divide-line-color : #e5e5e5; + +// 默认头像大小,如需要修改此值,注意同步修改 js 中的值 const avatarWidth = xx ,目前只支持方形头像 +// nvue 页面不支持修改头像大小 +$avatar-width : 45px ; + +// 头像边框 +$avatar-border-radius: 5px; +$avatar-border-color: #eee; +$avatar-border-width: 1px; + +// 标题文字样式 +$title-size : 16px; +$title-color : #3b4144; +$title-weight : normal; + +// 描述文字样式 +$note-size : 12px; +$note-color : #999; +$note-weight : normal; + +// 右侧额外内容默认样式 +$right-text-size : 12px; +$right-text-color : #999; +$right-text-weight : normal; + +// 角标样式 +// nvue 页面不支持修改圆点位置以及大小 +// 角标在左侧时,角标的位置,默认为 0 ,负数左/下移动,正数右/上移动 +$badge-left: 0px; +$badge-top: 0px; + +// 显示圆点时,圆点大小 +$dot-width: 10px; +$dot-height: 10px; + +// 显示角标时,角标大小和字体大小 +$badge-size : 18px; +$badge-font : 12px; +// 显示角标时,角标前景色 +$badge-color : #fff; +// 显示角标时,角标背景色 +$badge-background-color : #ff5a5f; +// 显示角标时,角标左右间距 +$badge-space : 6px; + +// 状态样式 +// 选中颜色 +$hover : #f5f5f5; diff --git a/uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue b/uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue new file mode 100644 index 0000000..d49fd7c --- /dev/null +++ b/uniapp/uni_modules/uni-list/components/uni-list-chat/uni-list-chat.vue @@ -0,0 +1,593 @@ + + + + + diff --git a/uniapp/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue b/uniapp/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue new file mode 100644 index 0000000..a274ac8 --- /dev/null +++ b/uniapp/uni_modules/uni-list/components/uni-list-item/uni-list-item.vue @@ -0,0 +1,534 @@ + + + + + \ No newline at end of file diff --git a/uniapp/uni_modules/uni-list/components/uni-list/uni-list.vue b/uniapp/uni_modules/uni-list/components/uni-list/uni-list.vue new file mode 100644 index 0000000..6ef5972 --- /dev/null +++ b/uniapp/uni_modules/uni-list/components/uni-list/uni-list.vue @@ -0,0 +1,123 @@ + + + + diff --git a/uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.vue b/uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.vue new file mode 100644 index 0000000..3b4c5a2 --- /dev/null +++ b/uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.vue @@ -0,0 +1,65 @@ + + + + + diff --git a/uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.wxs b/uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.wxs new file mode 100644 index 0000000..818a6b7 --- /dev/null +++ b/uniapp/uni_modules/uni-list/components/uni-list/uni-refresh.wxs @@ -0,0 +1,87 @@ +var pullDown = { + threshold: 95, + maxHeight: 200, + callRefresh: 'onrefresh', + callPullingDown: 'onpullingdown', + refreshSelector: '.uni-refresh' +}; + +function ready(newValue, oldValue, ownerInstance, instance) { + var state = instance.getState() + state.canPullDown = newValue; + // console.log(newValue); +} + +function touchStart(e, instance) { + var state = instance.getState(); + state.refreshInstance = instance.selectComponent(pullDown.refreshSelector); + state.canPullDown = (state.refreshInstance != null && state.refreshInstance != undefined); + if (!state.canPullDown) { + return + } + + // console.log("touchStart"); + + state.height = 0; + state.touchStartY = e.touches[0].pageY || e.changedTouches[0].pageY; + state.refreshInstance.setStyle({ + 'height': 0 + }); + state.refreshInstance.callMethod("onchange", true); +} + +function touchMove(e, ownerInstance) { + var instance = e.instance; + var state = instance.getState(); + if (!state.canPullDown) { + return + } + + var oldHeight = state.height; + var endY = e.touches[0].pageY || e.changedTouches[0].pageY; + var height = endY - state.touchStartY; + if (height > pullDown.maxHeight) { + return; + } + + var refreshInstance = state.refreshInstance; + refreshInstance.setStyle({ + 'height': height + 'px' + }); + + height = height < pullDown.maxHeight ? height : pullDown.maxHeight; + state.height = height; + refreshInstance.callMethod(pullDown.callPullingDown, { + height: height + }); +} + +function touchEnd(e, ownerInstance) { + var state = e.instance.getState(); + if (!state.canPullDown) { + return + } + + state.refreshInstance.callMethod("onchange", false); + + var refreshInstance = state.refreshInstance; + if (state.height > pullDown.threshold) { + refreshInstance.callMethod(pullDown.callRefresh); + return; + } + + refreshInstance.setStyle({ + 'height': 0 + }); +} + +function propObserver(newValue, oldValue, instance) { + pullDown = newValue; +} + +module.exports = { + touchmove: touchMove, + touchstart: touchStart, + touchend: touchEnd, + propObserver: propObserver +} diff --git a/uniapp/uni_modules/uni-list/package.json b/uniapp/uni_modules/uni-list/package.json new file mode 100644 index 0000000..8350efc --- /dev/null +++ b/uniapp/uni_modules/uni-list/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-list", + "displayName": "uni-list 列表", + "version": "1.2.14", + "description": "List 组件 ,帮助使用者快速构建列表。", + "keywords": [ + "", + "uni-ui", + "uniui", + "列表", + "", + "list" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-badge", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-list/readme.md b/uniapp/uni_modules/uni-list/readme.md new file mode 100644 index 0000000..32c2865 --- /dev/null +++ b/uniapp/uni_modules/uni-list/readme.md @@ -0,0 +1,346 @@ +## List 列表 +> **组件名:uni-list** +> 代码块: `uList`、`uListItem` +> 关联组件:`uni-list-item`、`uni-badge`、`uni-icons`、`uni-list-chat`、`uni-list-ad` + + +List 列表组件,包含基本列表样式、可扩展插槽机制、长列表性能优化、多端兼容。 + +在vue页面里,它默认使用页面级滚动。在app-nvue页面里,它默认使用原生list组件滚动。这样的长列表,在滚动出屏幕外后,系统会回收不可见区域的渲染内存资源,不会造成滚动越长手机越卡的问题。 + +uni-list组件是父容器,里面的核心是uni-list-item子组件,它代表列表中的一个可重复行,子组件可以无限循环。 + +uni-list-item有很多风格,uni-list-item组件通过内置的属性,满足一些常用的场景。当内置属性不满足需求时,可以通过扩展插槽来自定义列表内容。 + +内置属性可以覆盖的场景包括:导航列表、设置列表、小图标列表、通信录列表、聊天记录列表。 + +涉及很多大图或丰富内容的列表,比如类今日头条的新闻列表、类淘宝的电商列表,需要通过扩展插槽实现。 + +下文均有样例给出。 + +uni-list不包含下拉刷新和上拉翻页。上拉翻页另见组件:[uni-load-more](https://ext.dcloud.net.cn/plugin?id=29) + + +### 安装方式 + +本组件符合[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)规范,`HBuilderX 2.5.5`起,只需将本组件导入项目,在页面`template`中即可直接使用,无需在页面中`import`和注册`components`。 + +如需通过`npm`方式使用`uni-ui`组件,另见文档:[https://ext.dcloud.net.cn/plugin?id=55](https://ext.dcloud.net.cn/plugin?id=55) + +> **注意事项** +> 为了避免错误使用,给大家带来不好的开发体验,请在使用组件前仔细阅读下面的注意事项,可以帮你避免一些错误。 +> - 组件需要依赖 `sass` 插件 ,请自行手动安装 +> - 组件内部依赖 `'uni-icons'` 、`uni-badge` 组件 +> - `uni-list` 和 `uni-list-item` 需要配套使用,暂不支持单独使用 `uni-list-item` +> - 只有开启点击反馈后,会有点击选中效果 +> - 使用插槽时,可以完全自定义内容 +> - note 、rightText 属性暂时没做限制,不支持文字溢出隐藏,使用时应该控制长度显示或通过默认插槽自行扩展 +> - 支付宝小程序平台需要在支付宝小程序开发者工具里开启 component2 编译模式,开启方式: 详情 --> 项目配置 --> 启用 component2 编译 +> - 如果需要修改 `switch`、`badge` 样式,请使用插槽自定义 +> - 在 `HBuilderX` 低版本中,可能会出现组件显示 `undefined` 的问题,请升级最新的 `HBuilderX` 或者 `cli` +> - 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + +### 基本用法 + +- 设置 `title` 属性,可以显示列表标题 +- 设置 `disabled` 属性,可以禁用当前项 + +```html + + + + + +``` + +### 多行内容显示 + +- 设置 `note` 属性 ,可以在第二行显示描述文本信息 + +```html + + + + + +``` + +### 右侧显示角标、switch + +- 设置 `show-badge` 属性 ,可以显示角标内容 +- 设置 `show-switch` 属性,可以显示 switch 开关 + +```html + + + + + +``` + +### 左侧显示略缩图、图标 + +- 设置 `thumb` 属性 ,可以在列表左侧显示略缩图 +- 设置 `show-extra-icon` 属性,并指定 `extra-icon` 可以在左侧显示图标 + +```html + + + + +``` + +### 开启点击反馈和右侧箭头 +- 设置 `clickable` 为 `true` ,则表示这是一个可点击的列表,会默认给一个点击效果,并可以监听 `click` 事件 +- 设置 `link` 属性,会自动开启点击反馈,并给列表右侧添加一个箭头 +- 设置 `to` 属性,可以跳转页面,`link` 的值表示跳转方式,如果不指定,默认为 `navigateTo` + +```html + + + + + + + +``` + + +### 聊天列表示例 +- 设置 `clickable` 为 `true` ,则表示这是一个可点击的列表,会默认给一个点击效果,并可以监听 `click` 事件 +- 设置 `link` 属性,会自动开启点击反馈,`link` 的值表示跳转方式,如果不指定,默认为 `navigateTo` +- 设置 `to` 属性,可以跳转页面 +- `time` 属性,通常会设置成时间显示,但是这个属性不仅仅可以设置时间,你可以传入任何文本,注意文本长度可能会影响显示 +- `avatar` 和 `avatarList` 属性同时只会有一个生效,同时设置的话,`avatarList` 属性的长度大于1 ,`avatar` 属性将失效 +- 可以通过默认插槽自定义列表右侧内容 + +```html + + + + + + + + + + + + + + + + + 刚刚 + + + + + + + +``` + +```javascript + +export default { + components: {}, + data() { + return { + avatarList: [{ + url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png' + }, { + url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png' + }, { + url: 'https://vkceyugu.cdn.bspapp.com/VKCEYUGU-dc-site/460d46d0-4fcc-11eb-8ff1-d5dcf8779628.png' + }] + } + } +} + +``` + + +```css + +.chat-custom-right { + flex: 1; + /* #ifndef APP-NVUE */ + display: flex; + /* #endif */ + flex-direction: column; + justify-content: space-between; + align-items: flex-end; +} + +.chat-custom-text { + font-size: 12px; + color: #999; +} + +``` + +## API + +### List Props + +属性名 |类型 |默认值 | 说明 +:-: |:-: |:-: | :-: +border |Boolean |true | 是否显示边框 + + +### ListItem Props + +属性名 |类型 |默认值 | 说明 +:-: |:-: |:-: | :-: +title |String |- | 标题 +note |String |- | 描述 +ellipsis |Number |0 | title 是否溢出隐藏,可选值,0:默认; 1:显示一行; 2:显示两行;【nvue 暂不支持】 +thumb |String |- | 左侧缩略图,若thumb有值,则不会显示扩展图标 +thumbSize |String |medium | 略缩图尺寸,可选值,lg:大图; medium:一般; sm:小图; +showBadge |Boolean |false | 是否显示数字角标 +badgeText |String |- | 数字角标内容 +badgeType |String |- | 数字角标类型,参考[uni-icons](https://ext.dcloud.net.cn/plugin?id=21) +badgeStyle |Object |- | 数字角标样式,使用uni-badge的custom-style参数 +rightText |String |- | 右侧文字内容 +disabled |Boolean |false | 是否禁用 +showArrow |Boolean |true | 是否显示箭头图标 +link |String |navigateTo | 新页面跳转方式,可选值见下表 +to |String |- | 新页面跳转地址,如填写此属性,click 会返回页面是否跳转成功 +clickable |Boolean |false | 是否开启点击反馈 +showSwitch |Boolean |false | 是否显示Switch +switchChecked |Boolean |false | Switch是否被选中 +showExtraIcon |Boolean |false | 左侧是否显示扩展图标 +extraIcon |Object |- | 扩展图标参数,格式为 ``{color: '#4cd964',size: '22',type: 'spinner'}``,参考 [uni-icons](https://ext.dcloud.net.cn/plugin?id=28) +direction | String |row | 排版方向,可选值,row:水平排列; column:垂直排列; 3个插槽是水平排还是垂直排,也受此属性控制 + + +#### Link Options + +属性名 | 说明 +:-: | :-: +navigateTo | 同 uni.navigateTo() +redirectTo | 同 uni.reLaunch() +reLaunch | 同 uni.reLaunch() +switchTab | 同 uni.switchTab() + +### ListItem Events + +事件称名 |说明 |返回参数 +:-: |:-: |:-: +click |点击 uniListItem 触发事件,需开启点击反馈 |- +switchChange |点击切换 Switch 时触发,需显示 switch |e={value:checked} + + + +### ListItem Slots + +名称 | 说明 +:-: | :-: +header | 左/上内容插槽,可完全自定义默认显示 +body | 中间内容插槽,可完全自定义中间内容 +footer | 右/下内容插槽,可完全自定义右侧内容 + + +> **通过插槽扩展** +> 需要注意的是当使用插槽时,内置样式将会失效,只保留排版样式,此时的样式需要开发者自己实现 +> 如果 `uni-list-item` 组件内置属性样式无法满足需求,可以使用插槽来自定义uni-list-item里的内容。 +> uni-list-item提供了3个可扩展的插槽:`header`、`body`、`footer` +> - 当 `direction` 属性为 `row` 时表示水平排列,此时 `header` 表示列表的左边部分,`body` 表示列表的中间部分,`footer` 表示列表的右边部分 +> - 当 `direction` 属性为 `column` 时表示垂直排列,此时 `header` 表示列表的上边部分,`body` 表示列表的中间部分,`footer` 表示列表的下边部分 +> 开发者可以只用1个插槽,也可以3个一起使用。在插槽中可自主编写view标签,实现自己所需的效果。 + + +**示例** + +```html + + + + + + + + + 自定义插槽 + + + + +``` + + + + + +### ListItemChat Props + +属性名 |类型 |默认值 | 说明 +:-: |:-: |:-: | :-: +title |String |- | 标题 +note |String |- | 描述 +clickable |Boolean |false | 是否开启点击反馈 +badgeText |String |- | 数字角标内容,设置为 `dot` 将显示圆点 +badgePositon |String |right | 角标位置 +link |String |navigateTo | 是否展示右侧箭头并开启点击反馈,可选值见下表 +clickable |Boolean |false | 是否开启点击反馈 +to |String |- | 跳转页面地址,如填写此属性,click 会返回页面是否跳转成功 +time |String |- | 右侧时间显示 +avatarCircle |Boolean |false | 是否显示圆形头像 +avatar |String |- | 头像地址,avatarCircle 不填时生效 +avatarList |Array |- | 头像组,格式为 [{url:''}] + +#### Link Options + +属性名 | 说明 +:-: | :-: +navigateTo | 同 uni.navigateTo() +redirectTo | 同 uni.reLaunch() +reLaunch | 同 uni.reLaunch() +switchTab | 同 uni.switchTab() + +### ListItemChat Slots + +名称 | 说明 +:- | :- +default | 自定义列表右侧内容(包括时间和角标显示) + +### ListItemChat Events +事件称名 | 说明 | 返回参数 +:-: | :-: | :-: +@click | 点击 uniListChat 触发事件 | {data:{}} ,如有 to 属性,会返回页面跳转信息 + + + + + + +## 基于uni-list扩展的页面模板 + +通过扩展插槽,可实现多种常见样式的列表 + +**新闻列表类** + +1. 云端一体混合布局:[https://ext.dcloud.net.cn/plugin?id=2546](https://ext.dcloud.net.cn/plugin?id=2546) +2. 云端一体垂直布局,大图模式:[https://ext.dcloud.net.cn/plugin?id=2583](https://ext.dcloud.net.cn/plugin?id=2583) +3. 云端一体垂直布局,多行图文混排:[https://ext.dcloud.net.cn/plugin?id=2584](https://ext.dcloud.net.cn/plugin?id=2584) +4. 云端一体垂直布局,多图模式:[https://ext.dcloud.net.cn/plugin?id=2585](https://ext.dcloud.net.cn/plugin?id=2585) +5. 云端一体水平布局,左图右文:[https://ext.dcloud.net.cn/plugin?id=2586](https://ext.dcloud.net.cn/plugin?id=2586) +6. 云端一体水平布局,左文右图:[https://ext.dcloud.net.cn/plugin?id=2587](https://ext.dcloud.net.cn/plugin?id=2587) +7. 云端一体垂直布局,无图模式,主标题+副标题:[https://ext.dcloud.net.cn/plugin?id=2588](https://ext.dcloud.net.cn/plugin?id=2588) + +**商品列表类** + +1. 云端一体列表/宫格视图互切:[https://ext.dcloud.net.cn/plugin?id=2651](https://ext.dcloud.net.cn/plugin?id=2651) +2. 云端一体列表(宫格模式):[https://ext.dcloud.net.cn/plugin?id=2671](https://ext.dcloud.net.cn/plugin?id=2671) +3. 云端一体列表(列表模式):[https://ext.dcloud.net.cn/plugin?id=2672](https://ext.dcloud.net.cn/plugin?id=2672) + +## 组件示例 + +点击查看:[https://hellouniapp.dcloud.net.cn/pages/extUI/list/list](https://hellouniapp.dcloud.net.cn/pages/extUI/list/list) \ No newline at end of file diff --git a/uniapp/uni_modules/uni-load-more/changelog.md b/uniapp/uni_modules/uni-load-more/changelog.md new file mode 100644 index 0000000..8f03f1d --- /dev/null +++ b/uniapp/uni_modules/uni-load-more/changelog.md @@ -0,0 +1,19 @@ +## 1.3.3(2022-01-20) +- 新增 showText属性 ,是否显示文本 +## 1.3.2(2022-01-19) +- 修复 nvue 平台下不显示文本的bug +## 1.3.1(2022-01-19) +- 修复 微信小程序平台样式选择器报警告的问题 +## 1.3.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-load-more](https://uniapp.dcloud.io/component/uniui/uni-load-more) +## 1.2.1(2021-08-24) +- 新增 支持国际化 +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.8(2021-05-12) +- 新增 组件示例地址 +## 1.1.7(2021-03-30) +- 修复 uni-load-more 在首页使用时,h5 平台报 'uni is not defined' 的 bug +## 1.1.6(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/en.json b/uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/en.json new file mode 100644 index 0000000..a4f14a5 --- /dev/null +++ b/uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/en.json @@ -0,0 +1,5 @@ +{ + "uni-load-more.contentdown": "Pull up to show more", + "uni-load-more.contentrefresh": "loading...", + "uni-load-more.contentnomore": "No more data" +} diff --git a/uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/index.js b/uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json b/uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json new file mode 100644 index 0000000..f15d510 --- /dev/null +++ b/uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hans.json @@ -0,0 +1,5 @@ +{ + "uni-load-more.contentdown": "上拉显示更多", + "uni-load-more.contentrefresh": "正在加载...", + "uni-load-more.contentnomore": "没有更多数据了" +} diff --git a/uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json b/uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json new file mode 100644 index 0000000..a255c6d --- /dev/null +++ b/uniapp/uni_modules/uni-load-more/components/uni-load-more/i18n/zh-Hant.json @@ -0,0 +1,5 @@ +{ + "uni-load-more.contentdown": "上拉顯示更多", + "uni-load-more.contentrefresh": "正在加載...", + "uni-load-more.contentnomore": "沒有更多數據了" +} diff --git a/uniapp/uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue b/uniapp/uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue new file mode 100644 index 0000000..e5eff4d --- /dev/null +++ b/uniapp/uni_modules/uni-load-more/components/uni-load-more/uni-load-more.vue @@ -0,0 +1,399 @@ + + + + + diff --git a/uniapp/uni_modules/uni-load-more/package.json b/uniapp/uni_modules/uni-load-more/package.json new file mode 100644 index 0000000..2fa6f04 --- /dev/null +++ b/uniapp/uni_modules/uni-load-more/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-load-more", + "displayName": "uni-load-more 加载更多", + "version": "1.3.3", + "description": "LoadMore 组件,常用在列表里面,做滚动加载使用。", + "keywords": [ + "uni-ui", + "uniui", + "加载更多", + "load-more" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-load-more/readme.md b/uniapp/uni_modules/uni-load-more/readme.md new file mode 100644 index 0000000..54dc1fa --- /dev/null +++ b/uniapp/uni_modules/uni-load-more/readme.md @@ -0,0 +1,14 @@ + + +### LoadMore 加载更多 +> **组件名:uni-load-more** +> 代码块: `uLoadMore` + + +用于列表中,做滚动加载使用,展示 loading 的各种状态。 + + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-load-more) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/uniapp/uni_modules/uni-nav-bar/changelog.md b/uniapp/uni_modules/uni-nav-bar/changelog.md new file mode 100644 index 0000000..0f9a2f1 --- /dev/null +++ b/uniapp/uni_modules/uni-nav-bar/changelog.md @@ -0,0 +1,51 @@ +## 1.3.11(2023-03-29) +- 修复 自定义状态栏高度闪动BUG +## 1.3.10(2023-03-29) +- 修复 暗黑模式下边线颜色错误的bug +## 1.3.9(2022-10-13) +- 修复 条件编译错误的bug +## 1.3.8(2022-10-12) +- 修复 nvue 环境 fixed 为 true 的情况下,无法置顶的 bug +## 1.3.7(2022-08-11) +- 修复 nvue 环境下 fixed 为 true 的情况下,无法置顶的 bug +## 1.3.6(2022-06-30) +- 修复 组件示例中插槽用法无法显示内容的bug +## 1.3.5(2022-05-24) +- 新增 stat 属性 ,可开启统计title 上报 ,仅使用了title 属性且项目开启了uni统计生效 +## 1.3.4(2022-01-24) +- 更新 组件示例 +## 1.3.3(2022-01-24) +- 新增 left-width/right-width属性 ,可修改左右两侧的宽度 +## 1.3.2(2022-01-18) +- 修复 在vue下,标题不垂直居中的bug +## 1.3.1(2022-01-18) +- 修复 height 属性类型错误 +## 1.3.0(2022-01-18) +- 新增 height 属性,可修改组件高度 +- 新增 dark 属性可可开启暗黑模式 +- 优化 标题字数过多显示省略号 +- 优化 插槽,插入内容可完全覆盖 +## 1.2.1(2022-01-10) +- 修复 color 属性不生效的bug +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-nav-bar](https://uniapp.dcloud.io/component/uniui/uni-nav-bar) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.11(2021-05-12) +- 新增 组件示例地址 +## 1.0.10(2021-04-30) +- 修复 在nvue下fixed为true,宽度不能撑满的Bug +## 1.0.9(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.8(2021-04-14) +- uni-ui 修复 uni-nav-bar 当 fixed 属性为 true 时铺不满屏幕的 bug + +## 1.0.7(2021-02-25) +- 修复 easycom 下,找不到 uni-status-bar 的bug + +## 1.0.6(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.5(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue b/uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue new file mode 100644 index 0000000..c890860 --- /dev/null +++ b/uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-nav-bar.vue @@ -0,0 +1,357 @@ + + + + + diff --git a/uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue b/uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue new file mode 100644 index 0000000..4ac73ae --- /dev/null +++ b/uniapp/uni_modules/uni-nav-bar/components/uni-nav-bar/uni-status-bar.vue @@ -0,0 +1,24 @@ + + + + + diff --git a/uniapp/uni_modules/uni-nav-bar/package.json b/uniapp/uni_modules/uni-nav-bar/package.json new file mode 100644 index 0000000..240ae95 --- /dev/null +++ b/uniapp/uni_modules/uni-nav-bar/package.json @@ -0,0 +1,86 @@ +{ + "id": "uni-nav-bar", + "displayName": "uni-nav-bar 自定义导航栏", + "version": "1.3.11", + "description": "自定义导航栏组件,主要用于头部导航。", + "keywords": [ + "uni-ui", + "导航", + "导航栏", + "自定义导航栏" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-nav-bar/readme.md b/uniapp/uni_modules/uni-nav-bar/readme.md new file mode 100644 index 0000000..3934b32 --- /dev/null +++ b/uniapp/uni_modules/uni-nav-bar/readme.md @@ -0,0 +1,15 @@ + + +## NavBar 导航栏 +> **组件名:uni-nav-bar** +> 代码块: `uNavBar` + +导航栏组件,主要用于头部导航。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-nav-bar) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + + + + diff --git a/uniapp/uni_modules/uni-notice-bar/changelog.md b/uniapp/uni_modules/uni-notice-bar/changelog.md new file mode 100644 index 0000000..d526811 --- /dev/null +++ b/uniapp/uni_modules/uni-notice-bar/changelog.md @@ -0,0 +1,18 @@ +## 1.2.1(2022-09-05) +- 新增 属性 fontSize,可修改文字大小。 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-notice-bar](https://uniapp.dcloud.io/component/uniui/uni-notice-bar) +## 1.1.1(2021-11-09) +- 新增 提供组件设计资源,组件样式调整 +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.9(2021-05-12) +- 新增 组件示例地址 +## 1.0.8(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.7(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.6(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-notice-bar/components/uni-notice-bar/uni-notice-bar.vue b/uniapp/uni_modules/uni-notice-bar/components/uni-notice-bar/uni-notice-bar.vue new file mode 100644 index 0000000..98d4720 --- /dev/null +++ b/uniapp/uni_modules/uni-notice-bar/components/uni-notice-bar/uni-notice-bar.vue @@ -0,0 +1,426 @@ + + + + + diff --git a/uniapp/uni_modules/uni-notice-bar/package.json b/uniapp/uni_modules/uni-notice-bar/package.json new file mode 100644 index 0000000..8d9b13c --- /dev/null +++ b/uniapp/uni_modules/uni-notice-bar/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-notice-bar", + "displayName": "uni-notice-bar 通告栏", + "version": "1.2.1", + "description": "NoticeBar 通告栏组件,常用于展示公告信息,可设为滚动公告", + "keywords": [ + "uni-ui", + "uniui", + "通告栏", + "公告", + "跑马灯" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uniapp/uni_modules/uni-notice-bar/readme.md b/uniapp/uni_modules/uni-notice-bar/readme.md new file mode 100644 index 0000000..fb2ede2 --- /dev/null +++ b/uniapp/uni_modules/uni-notice-bar/readme.md @@ -0,0 +1,13 @@ + + +## NoticeBar 通告栏 +> **组件名:uni-notice-bar** +> 代码块: `uNoticeBar` + + +通告栏组件 。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-notice-bar) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/uniapp/uni_modules/uni-number-box/changelog.md b/uniapp/uni_modules/uni-number-box/changelog.md new file mode 100644 index 0000000..5925c32 --- /dev/null +++ b/uniapp/uni_modules/uni-number-box/changelog.md @@ -0,0 +1,25 @@ +## 1.2.1(2021-11-22) +- 修复 vue3中某些scss变量无法找到的问题 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-number-box](https://uniapp.dcloud.io/component/uniui/uni-number-box) +## 1.1.2(2021-11-09) +- 新增 提供组件设计资源,组件样式调整 +## 1.1.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-04-20) +- 修复 uni-number-box 浮点数运算不精确的 bug +- 修复 uni-number-box change 事件触发不正确的 bug +- 新增 uni-number-box v-model 双向绑定 +## 1.0.5(2021-02-05) +- 调整为uni_modules目录规范 + +## 1.0.7(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持 v-model +- 新增 支持 focus、blur 事件 +- 新增 支持 PC 端 diff --git a/uniapp/uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue b/uniapp/uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue new file mode 100644 index 0000000..e91c032 --- /dev/null +++ b/uniapp/uni_modules/uni-number-box/components/uni-number-box/uni-number-box.vue @@ -0,0 +1,221 @@ + + + diff --git a/uniapp/uni_modules/uni-number-box/package.json b/uniapp/uni_modules/uni-number-box/package.json new file mode 100644 index 0000000..ad82336 --- /dev/null +++ b/uniapp/uni_modules/uni-number-box/package.json @@ -0,0 +1,85 @@ +{ + "id": "uni-number-box", + "displayName": "uni-number-box 数字输入框", + "version": "1.2.1", + "description": "NumberBox 带加减按钮的数字输入框组件,用户可以控制每次点击增加的数值,支持小数。", + "keywords": [ + "uni-ui", + "uniui", + "数字输入框" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-number-box/readme.md b/uniapp/uni_modules/uni-number-box/readme.md new file mode 100644 index 0000000..affc56f --- /dev/null +++ b/uniapp/uni_modules/uni-number-box/readme.md @@ -0,0 +1,13 @@ + + +## NumberBox 数字输入框 +> **组件名:uni-number-box** +> 代码块: `uNumberBox` + + +带加减按钮的数字输入框。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-number-box) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/uniapp/uni_modules/uni-pagination/changelog.md b/uniapp/uni_modules/uni-pagination/changelog.md new file mode 100644 index 0000000..2e94adc --- /dev/null +++ b/uniapp/uni_modules/uni-pagination/changelog.md @@ -0,0 +1,27 @@ +## 1.2.4(2022-09-19) +- 修复,未对主题色设置默认色,导致未引入 uni-scss 变量文件报错。 +- 修复,未对移动端当前页文字做主题色适配。 +## 1.2.3(2022-09-15) +- 修复未使用 uni-scss 主题色的 bug。 +## 1.2.2(2022-07-06) +- 修复 es 语言 i18n 错误 +## 1.2.1(2021-11-22) +- 修复 vue3中某些scss变量无法找到的问题 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-pagination](https://uniapp.dcloud.io/component/uniui/uni-pagination) +## 1.1.2(2021-10-08) +- 修复 current 、value 属性未监听,导致高亮样式失效的 bug +## 1.1.1(2021-08-20) +- 新增 支持国际化 +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-05-12) +- 新增 组件示例地址 +## 1.0.6(2021-04-12) +- 新增 PC 和 移动端适配不同的 ui +## 1.0.5(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/en.json b/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/en.json new file mode 100644 index 0000000..d6e2897 --- /dev/null +++ b/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/en.json @@ -0,0 +1,5 @@ +{ + "uni-pagination.prevText": "prev", + "uni-pagination.nextText": "next", + "uni-pagination.piecePerPage": "piece/page" +} diff --git a/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/es.json b/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/es.json new file mode 100644 index 0000000..604a113 --- /dev/null +++ b/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/es.json @@ -0,0 +1,5 @@ +{ + "uni-pagination.prevText": "anterior", + "uni-pagination.nextText": "prxima", + "uni-pagination.piecePerPage": "Artculo/Pgina" +} diff --git a/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/fr.json b/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/fr.json new file mode 100644 index 0000000..a7a0c77 --- /dev/null +++ b/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/fr.json @@ -0,0 +1,5 @@ +{ + "uni-pagination.prevText": "précédente", + "uni-pagination.nextText": "suivante", + "uni-pagination.piecePerPage": "Articles/Pages" +} diff --git a/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/index.js b/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/index.js new file mode 100644 index 0000000..2469dd0 --- /dev/null +++ b/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/index.js @@ -0,0 +1,12 @@ +import en from './en.json' +import es from './es.json' +import fr from './fr.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + es, + fr, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hans.json b/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hans.json new file mode 100644 index 0000000..782bbe4 --- /dev/null +++ b/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hans.json @@ -0,0 +1,5 @@ +{ + "uni-pagination.prevText": "上一页", + "uni-pagination.nextText": "下一页", + "uni-pagination.piecePerPage": "条/页" +} diff --git a/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hant.json b/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hant.json new file mode 100644 index 0000000..180fddb --- /dev/null +++ b/uniapp/uni_modules/uni-pagination/components/uni-pagination/i18n/zh-Hant.json @@ -0,0 +1,5 @@ +{ + "uni-pagination.prevText": "上一頁", + "uni-pagination.nextText": "下一頁", + "uni-pagination.piecePerPage": "條/頁" +} diff --git a/uniapp/uni_modules/uni-pagination/components/uni-pagination/uni-pagination.vue b/uniapp/uni_modules/uni-pagination/components/uni-pagination/uni-pagination.vue new file mode 100644 index 0000000..5305b5f --- /dev/null +++ b/uniapp/uni_modules/uni-pagination/components/uni-pagination/uni-pagination.vue @@ -0,0 +1,465 @@ + + + + + diff --git a/uniapp/uni_modules/uni-pagination/package.json b/uniapp/uni_modules/uni-pagination/package.json new file mode 100644 index 0000000..862d5ab --- /dev/null +++ b/uniapp/uni_modules/uni-pagination/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-pagination", + "displayName": "uni-pagination 分页器", + "version": "1.2.4", + "description": "Pagination 分页器组件,用于展示页码、请求数据等。", + "keywords": [ + "uni-ui", + "uniui", + "分页器", + "页码" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss","uni-icons"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-pagination/readme.md b/uniapp/uni_modules/uni-pagination/readme.md new file mode 100644 index 0000000..97ea1d6 --- /dev/null +++ b/uniapp/uni_modules/uni-pagination/readme.md @@ -0,0 +1,11 @@ + + +## Pagination 分页器 +> **组件名:uni-pagination** +> 代码块: `uPagination` + + +分页器组件,用于展示页码、请求数据等。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-pagination) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/uniapp/uni_modules/uni-popup/changelog.md b/uniapp/uni_modules/uni-popup/changelog.md new file mode 100644 index 0000000..bc59f07 --- /dev/null +++ b/uniapp/uni_modules/uni-popup/changelog.md @@ -0,0 +1,68 @@ +## 1.8.3(2023-04-17) +- 修复 uni-popup 重复打开时的 bug +## 1.8.2(2023-02-02) +- uni-popup-dialog 组件新增 inputType 属性 +## 1.8.1(2022-12-01) +- 修复 nvue 下 v-show 报错 +## 1.8.0(2022-11-29) +- 优化 主题样式 +## 1.7.9(2022-04-02) +- 修复 弹出层内部无法滚动的bug +## 1.7.8(2022-03-28) +- 修复 小程序中高度错误的bug +## 1.7.7(2022-03-17) +- 修复 快速调用open出现问题的Bug +## 1.7.6(2022-02-14) +- 修复 safeArea 属性不能设置为false的bug +## 1.7.5(2022-01-19) +- 修复 isMaskClick 失效的bug +## 1.7.4(2022-01-19) +- 新增 cancelText \ confirmText 属性 ,可自定义文本 +- 新增 maskBackgroundColor 属性 ,可以修改蒙版颜色 +- 优化 maskClick属性 更新为 isMaskClick ,解决微信小程序警告的问题 +## 1.7.3(2022-01-13) +- 修复 设置 safeArea 属性不生效的bug +## 1.7.2(2021-11-26) +- 优化 组件示例 +## 1.7.1(2021-11-26) +- 修复 vuedoc 文字错误 +## 1.7.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-popup](https://uniapp.dcloud.io/component/uniui/uni-popup) +## 1.6.2(2021-08-24) +- 新增 支持国际化 +## 1.6.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.6.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.5.0(2021-06-23) +- 新增 mask-click 遮罩层点击事件 +## 1.4.5(2021-06-22) +- 修复 nvue 平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug +## 1.4.4(2021-06-18) +- 修复 H5平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug +## 1.4.3(2021-06-08) +- 修复 错误的 watch 字段 +- 修复 safeArea 属性不生效的问题 +- 修复 点击内容,再点击遮罩无法关闭的Bug +## 1.4.2(2021-05-12) +- 新增 组件示例地址 +## 1.4.1(2021-04-29) +- 修复 组件内放置 input 、textarea 组件,无法聚焦的问题 +## 1.4.0 (2021-04-29) +- 新增 type 属性的 left\right 值,支持左右弹出 +- 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗 +- 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色 +- 新增 safeArea 属性,是否适配底部安全区 +- 修复 App\h5\微信小程序底部安全区占位不对的Bug +- 修复 App 端弹出等待的Bug +- 优化 提升低配设备性能,优化动画卡顿问题 +- 优化 更简单的组件自定义方式 +## 1.2.9(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 +## 1.2.8(2021-02-05) +- 调整为uni_modules目录规范 +## 1.2.7(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持 PC 端 +- 新增 uni-popup-message 、uni-popup-dialog扩展组件支持 PC 端 diff --git a/uniapp/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js b/uniapp/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js new file mode 100644 index 0000000..6ef26a2 --- /dev/null +++ b/uniapp/uni_modules/uni-popup/components/uni-popup-dialog/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false + } + }, + mounted () { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'] + } + const listener = ($event) => { + if (this.disable) { + return + } + const keyName = Object.keys(keyNames).find(key => { + const keyName = $event.key + const value = keyNames[key] + return value === keyName || (Array.isArray(value) && value.includes(keyName)) + }) + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}) + }, 0) + } + } + document.addEventListener('keyup', listener) + this.$once('hook:beforeDestroy', () => { + document.removeEventListener('keyup', listener) + }) + }, + render: () => {} +} +// #endif diff --git a/uniapp/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue b/uniapp/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue new file mode 100644 index 0000000..b5eee79 --- /dev/null +++ b/uniapp/uni_modules/uni-popup/components/uni-popup-dialog/uni-popup-dialog.vue @@ -0,0 +1,275 @@ + + + + + diff --git a/uniapp/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue b/uniapp/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue new file mode 100644 index 0000000..91370a8 --- /dev/null +++ b/uniapp/uni_modules/uni-popup/components/uni-popup-message/uni-popup-message.vue @@ -0,0 +1,143 @@ + + + + diff --git a/uniapp/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue b/uniapp/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue new file mode 100644 index 0000000..5be7624 --- /dev/null +++ b/uniapp/uni_modules/uni-popup/components/uni-popup-share/uni-popup-share.vue @@ -0,0 +1,187 @@ + + + + diff --git a/uniapp/uni_modules/uni-popup/components/uni-popup/i18n/en.json b/uniapp/uni_modules/uni-popup/components/uni-popup/i18n/en.json new file mode 100644 index 0000000..7f1bd06 --- /dev/null +++ b/uniapp/uni_modules/uni-popup/components/uni-popup/i18n/en.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "cancel", + "uni-popup.ok": "ok", + "uni-popup.placeholder": "pleace enter", + "uni-popup.title": "Hint", + "uni-popup.shareTitle": "Share to" +} diff --git a/uniapp/uni_modules/uni-popup/components/uni-popup/i18n/index.js b/uniapp/uni_modules/uni-popup/components/uni-popup/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uniapp/uni_modules/uni-popup/components/uni-popup/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json b/uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json new file mode 100644 index 0000000..5e3003c --- /dev/null +++ b/uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hans.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "取消", + "uni-popup.ok": "确定", + "uni-popup.placeholder": "请输入", + "uni-popup.title": "提示", + "uni-popup.shareTitle": "分享到" +} diff --git a/uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json b/uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json new file mode 100644 index 0000000..13e39eb --- /dev/null +++ b/uniapp/uni_modules/uni-popup/components/uni-popup/i18n/zh-Hant.json @@ -0,0 +1,7 @@ +{ + "uni-popup.cancel": "取消", + "uni-popup.ok": "確定", + "uni-popup.placeholder": "請輸入", + "uni-popup.title": "提示", + "uni-popup.shareTitle": "分享到" +} diff --git a/uniapp/uni_modules/uni-popup/components/uni-popup/keypress.js b/uniapp/uni_modules/uni-popup/components/uni-popup/keypress.js new file mode 100644 index 0000000..62dda46 --- /dev/null +++ b/uniapp/uni_modules/uni-popup/components/uni-popup/keypress.js @@ -0,0 +1,45 @@ +// #ifdef H5 +export default { + name: 'Keypress', + props: { + disable: { + type: Boolean, + default: false + } + }, + mounted () { + const keyNames = { + esc: ['Esc', 'Escape'], + tab: 'Tab', + enter: 'Enter', + space: [' ', 'Spacebar'], + up: ['Up', 'ArrowUp'], + left: ['Left', 'ArrowLeft'], + right: ['Right', 'ArrowRight'], + down: ['Down', 'ArrowDown'], + delete: ['Backspace', 'Delete', 'Del'] + } + const listener = ($event) => { + if (this.disable) { + return + } + const keyName = Object.keys(keyNames).find(key => { + const keyName = $event.key + const value = keyNames[key] + return value === keyName || (Array.isArray(value) && value.includes(keyName)) + }) + if (keyName) { + // 避免和其他按键事件冲突 + setTimeout(() => { + this.$emit(keyName, {}) + }, 0) + } + } + document.addEventListener('keyup', listener) + // this.$once('hook:beforeDestroy', () => { + // document.removeEventListener('keyup', listener) + // }) + }, + render: () => {} +} +// #endif diff --git a/uniapp/uni_modules/uni-popup/components/uni-popup/popup.js b/uniapp/uni_modules/uni-popup/components/uni-popup/popup.js new file mode 100644 index 0000000..c4e5781 --- /dev/null +++ b/uniapp/uni_modules/uni-popup/components/uni-popup/popup.js @@ -0,0 +1,26 @@ + +export default { + data() { + return { + + } + }, + created(){ + this.popup = this.getParent() + }, + methods:{ + /** + * 获取父元素实例 + */ + getParent(name = 'uniPopup') { + let parent = this.$parent; + let parentName = parent.$options.name; + while (parentName !== name) { + parent = parent.$parent; + if (!parent) return false + parentName = parent.$options.name; + } + return parent; + }, + } +} diff --git a/uniapp/uni_modules/uni-popup/components/uni-popup/uni-popup.vue b/uniapp/uni_modules/uni-popup/components/uni-popup/uni-popup.vue new file mode 100644 index 0000000..54afee2 --- /dev/null +++ b/uniapp/uni_modules/uni-popup/components/uni-popup/uni-popup.vue @@ -0,0 +1,473 @@ + + + + diff --git a/uniapp/uni_modules/uni-popup/package.json b/uniapp/uni_modules/uni-popup/package.json new file mode 100644 index 0000000..f40556b --- /dev/null +++ b/uniapp/uni_modules/uni-popup/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-popup", + "displayName": "uni-popup 弹出层", + "version": "1.8.3", + "description": " Popup 组件,提供常用的弹层", + "keywords": [ + "uni-ui", + "弹出层", + "弹窗", + "popup", + "弹框" + ], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-transition" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uniapp/uni_modules/uni-popup/readme.md b/uniapp/uni_modules/uni-popup/readme.md new file mode 100644 index 0000000..fdad4b3 --- /dev/null +++ b/uniapp/uni_modules/uni-popup/readme.md @@ -0,0 +1,17 @@ + + +## Popup 弹出层 +> **组件名:uni-popup** +> 代码块: `uPopup` +> 关联组件:`uni-transition` + + +弹出层组件,在应用中弹出一个消息提示窗口、提示框等 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-popup) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + + + + diff --git a/uniapp/uni_modules/uni-rate/changelog.md b/uniapp/uni_modules/uni-rate/changelog.md new file mode 100644 index 0000000..8a98a61 --- /dev/null +++ b/uniapp/uni_modules/uni-rate/changelog.md @@ -0,0 +1,25 @@ +## 1.3.1(2022-02-25) +- 修复 条件判断 `NaN` 错误的 bug +## 1.3.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-rate](https://uniapp.dcloud.io/component/uniui/uni-rate) +## 1.2.2(2021-09-10) +- 优化 默认值修改为 0 颗星 +## 1.2.1(2021-07-30) +- 优化 vue3下事件警告的问题 +## 1.2.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.2(2021-05-12) +- 新增 组件示例地址 +## 1.1.1(2021-04-21) +- 修复 布局变化后 uni-rate 星星计算不准确的 bug +- 优化 添加依赖 uni-icons, 导入 uni-rate 自动下载依赖 +## 1.1.0(2021-04-16) +- 修复 uni-rate 属性 margin 值为 string 组件失效的 bug + +## 1.0.9(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.8(2021-02-05) +- 调整为uni_modules目录规范 +- 支持 pc 端 diff --git a/uniapp/uni_modules/uni-rate/components/uni-rate/uni-rate.vue b/uniapp/uni_modules/uni-rate/components/uni-rate/uni-rate.vue new file mode 100644 index 0000000..9802dfc --- /dev/null +++ b/uniapp/uni_modules/uni-rate/components/uni-rate/uni-rate.vue @@ -0,0 +1,365 @@ + + + + + diff --git a/uniapp/uni_modules/uni-rate/package.json b/uniapp/uni_modules/uni-rate/package.json new file mode 100644 index 0000000..64e8e33 --- /dev/null +++ b/uniapp/uni_modules/uni-rate/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-rate", + "displayName": "uni-rate 评分", + "version": "1.3.1", + "description": "Rate 评分组件,可自定义评分星星图标的大小、间隔、评分数。", + "keywords": [ + "uni-ui", + "uniui", + "评分" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uniapp/uni_modules/uni-rate/readme.md b/uniapp/uni_modules/uni-rate/readme.md new file mode 100644 index 0000000..eae7b5c --- /dev/null +++ b/uniapp/uni_modules/uni-rate/readme.md @@ -0,0 +1,12 @@ + + +## Rate 评分 +> **组件名:uni-rate** +> 代码块: `uRate` +> 关联组件:`uni-icons` + + +评分组件,多用于购买商品后,对商品进行评价等场景 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-rate) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-row/changelog.md b/uniapp/uni_modules/uni-row/changelog.md new file mode 100644 index 0000000..5b465bc --- /dev/null +++ b/uniapp/uni_modules/uni-row/changelog.md @@ -0,0 +1,10 @@ +## 1.0.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-row](https://uniapp.dcloud.io/component/uniui/uni-row) +## 0.1.0(2021-07-13) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 0.0.4(2021-05-12) +- 新增 组件示例地址 +## 0.0.3(2021-02-05) +- 调整为uni_modules目录规范 +- 新增uni-row组件 diff --git a/uniapp/uni_modules/uni-row/components/uni-col/uni-col.vue b/uniapp/uni_modules/uni-row/components/uni-col/uni-col.vue new file mode 100644 index 0000000..d5f3728 --- /dev/null +++ b/uniapp/uni_modules/uni-row/components/uni-col/uni-col.vue @@ -0,0 +1,317 @@ + + + + + diff --git a/uniapp/uni_modules/uni-row/components/uni-row/uni-row.vue b/uniapp/uni_modules/uni-row/components/uni-row/uni-row.vue new file mode 100644 index 0000000..c7d9370 --- /dev/null +++ b/uniapp/uni_modules/uni-row/components/uni-row/uni-row.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/uniapp/uni_modules/uni-row/package.json b/uniapp/uni_modules/uni-row/package.json new file mode 100644 index 0000000..3f52fa6 --- /dev/null +++ b/uniapp/uni_modules/uni-row/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-row", + "displayName": "uni-row 布局-行", + "version": "1.0.0", + "description": "流式栅格系统,随着屏幕或视口分为 24 份,可以迅速简便地创建布局。", + "keywords": [ + "uni-ui", + "uniui", + "栅格", + "布局", + "layout" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-row/readme.md b/uniapp/uni_modules/uni-row/readme.md new file mode 100644 index 0000000..3c9c8b9 --- /dev/null +++ b/uniapp/uni_modules/uni-row/readme.md @@ -0,0 +1,10 @@ +## Layout 布局 + +> **组件名 uni-row、uni-col** +> 代码块: `uRow`、`uCol` + + +流式栅格系统,随着屏幕或视口分为 24 份,可以迅速简便地创建布局。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-row) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-scss/changelog.md b/uniapp/uni_modules/uni-scss/changelog.md new file mode 100644 index 0000000..b863bb0 --- /dev/null +++ b/uniapp/uni_modules/uni-scss/changelog.md @@ -0,0 +1,8 @@ +## 1.0.3(2022-01-21) +- 优化 组件示例 +## 1.0.2(2021-11-22) +- 修复 / 符号在 vue 不同版本兼容问题引起的报错问题 +## 1.0.1(2021-11-22) +- 修复 vue3中scss语法兼容问题 +## 1.0.0(2021-11-18) +- init diff --git a/uniapp/uni_modules/uni-scss/index.scss b/uniapp/uni_modules/uni-scss/index.scss new file mode 100644 index 0000000..1744a5f --- /dev/null +++ b/uniapp/uni_modules/uni-scss/index.scss @@ -0,0 +1 @@ +@import './styles/index.scss'; diff --git a/uniapp/uni_modules/uni-scss/package.json b/uniapp/uni_modules/uni-scss/package.json new file mode 100644 index 0000000..7cc0ccb --- /dev/null +++ b/uniapp/uni_modules/uni-scss/package.json @@ -0,0 +1,82 @@ +{ + "id": "uni-scss", + "displayName": "uni-scss 辅助样式", + "version": "1.0.3", + "description": "uni-sass是uni-ui提供的一套全局样式 ,通过一些简单的类名和sass变量,实现简单的页面布局操作,比如颜色、边距、圆角等。", + "keywords": [ + "uni-scss", + "uni-ui", + "辅助样式" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.1.0" + }, + "dcloudext": { + "category": [ + "JS SDK", + "通用 SDK" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "n", + "联盟": "n" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uniapp/uni_modules/uni-scss/readme.md b/uniapp/uni_modules/uni-scss/readme.md new file mode 100644 index 0000000..b7d1c25 --- /dev/null +++ b/uniapp/uni_modules/uni-scss/readme.md @@ -0,0 +1,4 @@ +`uni-sass` 是 `uni-ui`提供的一套全局样式 ,通过一些简单的类名和`sass`变量,实现简单的页面布局操作,比如颜色、边距、圆角等。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-sass) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-scss/styles/index.scss b/uniapp/uni_modules/uni-scss/styles/index.scss new file mode 100644 index 0000000..ffac4fe --- /dev/null +++ b/uniapp/uni_modules/uni-scss/styles/index.scss @@ -0,0 +1,7 @@ +@import './setting/_variables.scss'; +@import './setting/_border.scss'; +@import './setting/_color.scss'; +@import './setting/_space.scss'; +@import './setting/_radius.scss'; +@import './setting/_text.scss'; +@import './setting/_styles.scss'; diff --git a/uniapp/uni_modules/uni-scss/styles/setting/_border.scss b/uniapp/uni_modules/uni-scss/styles/setting/_border.scss new file mode 100644 index 0000000..12a11c3 --- /dev/null +++ b/uniapp/uni_modules/uni-scss/styles/setting/_border.scss @@ -0,0 +1,3 @@ +.uni-border { + border: 1px $uni-border-1 solid; +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-scss/styles/setting/_color.scss b/uniapp/uni_modules/uni-scss/styles/setting/_color.scss new file mode 100644 index 0000000..1ededd9 --- /dev/null +++ b/uniapp/uni_modules/uni-scss/styles/setting/_color.scss @@ -0,0 +1,66 @@ + +// TODO 暂时不需要 class ,需要用户使用变量实现 ,如果使用类名其实并不推荐 +// @mixin get-styles($k,$c) { +// @if $k == size or $k == weight{ +// font-#{$k}:#{$c} +// }@else{ +// #{$k}:#{$c} +// } +// } +$uni-ui-color:( + // 主色 + primary: $uni-primary, + primary-disable: $uni-primary-disable, + primary-light: $uni-primary-light, + // 辅助色 + success: $uni-success, + success-disable: $uni-success-disable, + success-light: $uni-success-light, + warning: $uni-warning, + warning-disable: $uni-warning-disable, + warning-light: $uni-warning-light, + error: $uni-error, + error-disable: $uni-error-disable, + error-light: $uni-error-light, + info: $uni-info, + info-disable: $uni-info-disable, + info-light: $uni-info-light, + // 中性色 + main-color: $uni-main-color, + base-color: $uni-base-color, + secondary-color: $uni-secondary-color, + extra-color: $uni-extra-color, + // 背景色 + bg-color: $uni-bg-color, + // 边框颜色 + border-1: $uni-border-1, + border-2: $uni-border-2, + border-3: $uni-border-3, + border-4: $uni-border-4, + // 黑色 + black:$uni-black, + // 白色 + white:$uni-white, + // 透明 + transparent:$uni-transparent +) !default; +@each $key, $child in $uni-ui-color { + .uni-#{"" + $key} { + color: $child; + } + .uni-#{"" + $key}-bg { + background-color: $child; + } +} +.uni-shadow-sm { + box-shadow: $uni-shadow-sm; +} +.uni-shadow-base { + box-shadow: $uni-shadow-base; +} +.uni-shadow-lg { + box-shadow: $uni-shadow-lg; +} +.uni-mask { + background-color:$uni-mask; +} diff --git a/uniapp/uni_modules/uni-scss/styles/setting/_radius.scss b/uniapp/uni_modules/uni-scss/styles/setting/_radius.scss new file mode 100644 index 0000000..9a0428b --- /dev/null +++ b/uniapp/uni_modules/uni-scss/styles/setting/_radius.scss @@ -0,0 +1,55 @@ +@mixin radius($r,$d:null ,$important: false){ + $radius-value:map-get($uni-radius, $r) if($important, !important, null); + // Key exists within the $uni-radius variable + @if (map-has-key($uni-radius, $r) and $d){ + @if $d == t { + border-top-left-radius:$radius-value; + border-top-right-radius:$radius-value; + }@else if $d == r { + border-top-right-radius:$radius-value; + border-bottom-right-radius:$radius-value; + }@else if $d == b { + border-bottom-left-radius:$radius-value; + border-bottom-right-radius:$radius-value; + }@else if $d == l { + border-top-left-radius:$radius-value; + border-bottom-left-radius:$radius-value; + }@else if $d == tl { + border-top-left-radius:$radius-value; + }@else if $d == tr { + border-top-right-radius:$radius-value; + }@else if $d == br { + border-bottom-right-radius:$radius-value; + }@else if $d == bl { + border-bottom-left-radius:$radius-value; + } + }@else{ + border-radius:$radius-value; + } +} + +@each $key, $child in $uni-radius { + @if($key){ + .uni-radius-#{"" + $key} { + @include radius($key) + } + }@else{ + .uni-radius { + @include radius($key) + } + } +} + +@each $direction in t, r, b, l,tl, tr, br, bl { + @each $key, $child in $uni-radius { + @if($key){ + .uni-radius-#{"" + $direction}-#{"" + $key} { + @include radius($key,$direction,false) + } + }@else{ + .uni-radius-#{$direction} { + @include radius($key,$direction,false) + } + } + } +} diff --git a/uniapp/uni_modules/uni-scss/styles/setting/_space.scss b/uniapp/uni_modules/uni-scss/styles/setting/_space.scss new file mode 100644 index 0000000..3c89528 --- /dev/null +++ b/uniapp/uni_modules/uni-scss/styles/setting/_space.scss @@ -0,0 +1,56 @@ + +@mixin fn($space,$direction,$size,$n) { + @if $n { + #{$space}-#{$direction}: #{$size*$uni-space-root}px + } @else { + #{$space}-#{$direction}: #{-$size*$uni-space-root}px + } +} +@mixin get-styles($direction,$i,$space,$n){ + @if $direction == t { + @include fn($space, top,$i,$n); + } + @if $direction == r { + @include fn($space, right,$i,$n); + } + @if $direction == b { + @include fn($space, bottom,$i,$n); + } + @if $direction == l { + @include fn($space, left,$i,$n); + } + @if $direction == x { + @include fn($space, left,$i,$n); + @include fn($space, right,$i,$n); + } + @if $direction == y { + @include fn($space, top,$i,$n); + @include fn($space, bottom,$i,$n); + } + @if $direction == a { + @if $n { + #{$space}:#{$i*$uni-space-root}px; + } @else { + #{$space}:#{-$i*$uni-space-root}px; + } + } +} + +@each $orientation in m,p { + $space: margin; + @if $orientation == m { + $space: margin; + } @else { + $space: padding; + } + @for $i from 0 through 16 { + @each $direction in t, r, b, l, x, y, a { + .uni-#{$orientation}#{$direction}-#{$i} { + @include get-styles($direction,$i,$space,true); + } + .uni-#{$orientation}#{$direction}-n#{$i} { + @include get-styles($direction,$i,$space,false); + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-scss/styles/setting/_styles.scss b/uniapp/uni_modules/uni-scss/styles/setting/_styles.scss new file mode 100644 index 0000000..689afec --- /dev/null +++ b/uniapp/uni_modules/uni-scss/styles/setting/_styles.scss @@ -0,0 +1,167 @@ +/* #ifndef APP-NVUE */ + +$-color-white:#fff; +$-color-black:#000; +@mixin base-style($color) { + color: #fff; + background-color: $color; + border-color: mix($-color-black, $color, 8%); + &:not([hover-class]):active { + background: mix($-color-black, $color, 10%); + border-color: mix($-color-black, $color, 20%); + color: $-color-white; + outline: none; + } +} +@mixin is-color($color) { + @include base-style($color); + &[loading] { + @include base-style($color); + &::before { + margin-right:5px; + } + } + &[disabled] { + &, + &[loading], + &:not([hover-class]):active { + color: $-color-white; + border-color: mix(darken($color,10%), $-color-white); + background-color: mix($color, $-color-white); + } + } + +} +@mixin base-plain-style($color) { + color:$color; + background-color: mix($-color-white, $color, 90%); + border-color: mix($-color-white, $color, 70%); + &:not([hover-class]):active { + background: mix($-color-white, $color, 80%); + color: $color; + outline: none; + border-color: mix($-color-white, $color, 50%); + } +} +@mixin is-plain($color){ + &[plain] { + @include base-plain-style($color); + &[loading] { + @include base-plain-style($color); + &::before { + margin-right:5px; + } + } + &[disabled] { + &, + &:active { + color: mix($-color-white, $color, 40%); + background-color: mix($-color-white, $color, 90%); + border-color: mix($-color-white, $color, 80%); + } + } + } +} + + +.uni-btn { + margin: 5px; + color: #393939; + border:1px solid #ccc; + font-size: 16px; + font-weight: 200; + background-color: #F9F9F9; + // TODO 暂时处理边框隐藏一边的问题 + overflow: visible; + &::after{ + border: none; + } + + &:not([type]),&[type=default] { + color: #999; + &[loading] { + background: none; + &::before { + margin-right:5px; + } + } + + + + &[disabled]{ + color: mix($-color-white, #999, 60%); + &, + &[loading], + &:active { + color: mix($-color-white, #999, 60%); + background-color: mix($-color-white,$-color-black , 98%); + border-color: mix($-color-white, #999, 85%); + } + } + + &[plain] { + color: #999; + background: none; + border-color: $uni-border-1; + &:not([hover-class]):active { + background: none; + color: mix($-color-white, $-color-black, 80%); + border-color: mix($-color-white, $-color-black, 90%); + outline: none; + } + &[disabled]{ + &, + &[loading], + &:active { + background: none; + color: mix($-color-white, #999, 60%); + border-color: mix($-color-white, #999, 85%); + } + } + } + } + + &:not([hover-class]):active { + color: mix($-color-white, $-color-black, 50%); + } + + &[size=mini] { + font-size: 16px; + font-weight: 200; + border-radius: 8px; + } + + + + &.uni-btn-small { + font-size: 14px; + } + &.uni-btn-mini { + font-size: 12px; + } + + &.uni-btn-radius { + border-radius: 999px; + } + &[type=primary] { + @include is-color($uni-primary); + @include is-plain($uni-primary) + } + &[type=success] { + @include is-color($uni-success); + @include is-plain($uni-success) + } + &[type=error] { + @include is-color($uni-error); + @include is-plain($uni-error) + } + &[type=warning] { + @include is-color($uni-warning); + @include is-plain($uni-warning) + } + &[type=info] { + @include is-color($uni-info); + @include is-plain($uni-info) + } +} +/* #endif */ diff --git a/uniapp/uni_modules/uni-scss/styles/setting/_text.scss b/uniapp/uni_modules/uni-scss/styles/setting/_text.scss new file mode 100644 index 0000000..a34d08f --- /dev/null +++ b/uniapp/uni_modules/uni-scss/styles/setting/_text.scss @@ -0,0 +1,24 @@ +@mixin get-styles($k,$c) { + @if $k == size or $k == weight{ + font-#{$k}:#{$c} + }@else{ + #{$k}:#{$c} + } +} + +@each $key, $child in $uni-headings { + /* #ifndef APP-NVUE */ + .uni-#{$key} { + @each $k, $c in $child { + @include get-styles($k,$c) + } + } + /* #endif */ + /* #ifdef APP-NVUE */ + .container .uni-#{$key} { + @each $k, $c in $child { + @include get-styles($k,$c) + } + } + /* #endif */ +} diff --git a/uniapp/uni_modules/uni-scss/styles/setting/_variables.scss b/uniapp/uni_modules/uni-scss/styles/setting/_variables.scss new file mode 100644 index 0000000..557d3d7 --- /dev/null +++ b/uniapp/uni_modules/uni-scss/styles/setting/_variables.scss @@ -0,0 +1,146 @@ +// @use "sass:math"; +@import '../tools/functions.scss'; +// 间距基础倍数 +$uni-space-root: 2 !default; +// 边框半径默认值 +$uni-radius-root:5px !default; +$uni-radius: () !default; +// 边框半径断点 +$uni-radius: map-deep-merge( + ( + 0: 0, + // TODO 当前版本暂时不支持 sm 属性 + // 'sm': math.div($uni-radius-root, 2), + null: $uni-radius-root, + 'lg': $uni-radius-root * 2, + 'xl': $uni-radius-root * 6, + 'pill': 9999px, + 'circle': 50% + ), + $uni-radius +); +// 字体家族 +$body-font-family: 'Roboto', sans-serif !default; +// 文本 +$heading-font-family: $body-font-family !default; +$uni-headings: () !default; +$letterSpacing: -0.01562em; +$uni-headings: map-deep-merge( + ( + 'h1': ( + size: 32px, + weight: 300, + line-height: 50px, + // letter-spacing:-0.01562em + ), + 'h2': ( + size: 28px, + weight: 300, + line-height: 40px, + // letter-spacing: -0.00833em + ), + 'h3': ( + size: 24px, + weight: 400, + line-height: 32px, + // letter-spacing: normal + ), + 'h4': ( + size: 20px, + weight: 400, + line-height: 30px, + // letter-spacing: 0.00735em + ), + 'h5': ( + size: 16px, + weight: 400, + line-height: 24px, + // letter-spacing: normal + ), + 'h6': ( + size: 14px, + weight: 500, + line-height: 18px, + // letter-spacing: 0.0125em + ), + 'subtitle': ( + size: 12px, + weight: 400, + line-height: 20px, + // letter-spacing: 0.00937em + ), + 'body': ( + font-size: 14px, + font-weight: 400, + line-height: 22px, + // letter-spacing: 0.03125em + ), + 'caption': ( + 'size': 12px, + 'weight': 400, + 'line-height': 20px, + // 'letter-spacing': 0.03333em, + // 'text-transform': false + ) + ), + $uni-headings +); + + + +// 主色 +$uni-primary: #2979ff !default; +$uni-primary-disable:lighten($uni-primary,20%) !default; +$uni-primary-light: lighten($uni-primary,25%) !default; + +// 辅助色 +// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。 +$uni-success: #18bc37 !default; +$uni-success-disable:lighten($uni-success,20%) !default; +$uni-success-light: lighten($uni-success,25%) !default; + +$uni-warning: #f3a73f !default; +$uni-warning-disable:lighten($uni-warning,20%) !default; +$uni-warning-light: lighten($uni-warning,25%) !default; + +$uni-error: #e43d33 !default; +$uni-error-disable:lighten($uni-error,20%) !default; +$uni-error-light: lighten($uni-error,25%) !default; + +$uni-info: #8f939c !default; +$uni-info-disable:lighten($uni-info,20%) !default; +$uni-info-light: lighten($uni-info,25%) !default; + +// 中性色 +// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。 +$uni-main-color: #3a3a3a !default; // 主要文字 +$uni-base-color: #6a6a6a !default; // 常规文字 +$uni-secondary-color: #909399 !default; // 次要文字 +$uni-extra-color: #c7c7c7 !default; // 辅助说明 + +// 边框颜色 +$uni-border-1: #F0F0F0 !default; +$uni-border-2: #EDEDED !default; +$uni-border-3: #DCDCDC !default; +$uni-border-4: #B9B9B9 !default; + +// 常规色 +$uni-black: #000000 !default; +$uni-white: #ffffff !default; +$uni-transparent: rgba($color: #000000, $alpha: 0) !default; + +// 背景色 +$uni-bg-color: #f7f7f7 !default; + +/* 水平间距 */ +$uni-spacing-sm: 8px !default; +$uni-spacing-base: 15px !default; +$uni-spacing-lg: 30px !default; + +// 阴影 +$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5) !default; +$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2) !default; +$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5) !default; + +// 蒙版 +$uni-mask: rgba($color: #000000, $alpha: 0.4) !default; diff --git a/uniapp/uni_modules/uni-scss/styles/tools/functions.scss b/uniapp/uni_modules/uni-scss/styles/tools/functions.scss new file mode 100644 index 0000000..ac6f63e --- /dev/null +++ b/uniapp/uni_modules/uni-scss/styles/tools/functions.scss @@ -0,0 +1,19 @@ +// 合并 map +@function map-deep-merge($parent-map, $child-map){ + $result: $parent-map; + @each $key, $child in $child-map { + $parent-has-key: map-has-key($result, $key); + $parent-value: map-get($result, $key); + $parent-type: type-of($parent-value); + $child-type: type-of($child); + $parent-is-map: $parent-type == map; + $child-is-map: $child-type == map; + + @if (not $parent-has-key) or ($parent-type != $child-type) or (not ($parent-is-map and $child-is-map)){ + $result: map-merge($result, ( $key: $child )); + }@else { + $result: map-merge($result, ( $key: map-deep-merge($parent-value, $child) )); + } + } + @return $result; +}; diff --git a/uniapp/uni_modules/uni-scss/theme.scss b/uniapp/uni_modules/uni-scss/theme.scss new file mode 100644 index 0000000..80ee62f --- /dev/null +++ b/uniapp/uni_modules/uni-scss/theme.scss @@ -0,0 +1,31 @@ +// 间距基础倍数 +$uni-space-root: 2; +// 边框半径默认值 +$uni-radius-root:5px; +// 主色 +$uni-primary: #2979ff; +// 辅助色 +$uni-success: #4cd964; +// 警告色 +$uni-warning: #f0ad4e; +// 错误色 +$uni-error: #dd524d; +// 描述色 +$uni-info: #909399; +// 中性色 +$uni-main-color: #303133; +$uni-base-color: #606266; +$uni-secondary-color: #909399; +$uni-extra-color: #C0C4CC; +// 背景色 +$uni-bg-color: #f5f5f5; +// 边框颜色 +$uni-border-1: #DCDFE6; +$uni-border-2: #E4E7ED; +$uni-border-3: #EBEEF5; +$uni-border-4: #F2F6FC; + +// 常规色 +$uni-black: #000000; +$uni-white: #ffffff; +$uni-transparent: rgba($color: #000000, $alpha: 0); diff --git a/uniapp/uni_modules/uni-scss/variables.scss b/uniapp/uni_modules/uni-scss/variables.scss new file mode 100644 index 0000000..1c062d4 --- /dev/null +++ b/uniapp/uni_modules/uni-scss/variables.scss @@ -0,0 +1,62 @@ +@import './styles/setting/_variables.scss'; +// 间距基础倍数 +$uni-space-root: 2; +// 边框半径默认值 +$uni-radius-root:5px; + +// 主色 +$uni-primary: #2979ff; +$uni-primary-disable:mix(#fff,$uni-primary,50%); +$uni-primary-light: mix(#fff,$uni-primary,80%); + +// 辅助色 +// 除了主色外的场景色,需要在不同的场景中使用(例如危险色表示危险的操作)。 +$uni-success: #18bc37; +$uni-success-disable:mix(#fff,$uni-success,50%); +$uni-success-light: mix(#fff,$uni-success,80%); + +$uni-warning: #f3a73f; +$uni-warning-disable:mix(#fff,$uni-warning,50%); +$uni-warning-light: mix(#fff,$uni-warning,80%); + +$uni-error: #e43d33; +$uni-error-disable:mix(#fff,$uni-error,50%); +$uni-error-light: mix(#fff,$uni-error,80%); + +$uni-info: #8f939c; +$uni-info-disable:mix(#fff,$uni-info,50%); +$uni-info-light: mix(#fff,$uni-info,80%); + +// 中性色 +// 中性色用于文本、背景和边框颜色。通过运用不同的中性色,来表现层次结构。 +$uni-main-color: #3a3a3a; // 主要文字 +$uni-base-color: #6a6a6a; // 常规文字 +$uni-secondary-color: #909399; // 次要文字 +$uni-extra-color: #c7c7c7; // 辅助说明 + +// 边框颜色 +$uni-border-1: #F0F0F0; +$uni-border-2: #EDEDED; +$uni-border-3: #DCDCDC; +$uni-border-4: #B9B9B9; + +// 常规色 +$uni-black: #000000; +$uni-white: #ffffff; +$uni-transparent: rgba($color: #000000, $alpha: 0); + +// 背景色 +$uni-bg-color: #f7f7f7; + +/* 水平间距 */ +$uni-spacing-sm: 8px; +$uni-spacing-base: 15px; +$uni-spacing-lg: 30px; + +// 阴影 +$uni-shadow-sm:0 0 5px rgba($color: #d8d8d8, $alpha: 0.5); +$uni-shadow-base:0 1px 8px 1px rgba($color: #a5a5a5, $alpha: 0.2); +$uni-shadow-lg:0px 1px 10px 2px rgba($color: #a5a4a4, $alpha: 0.5); + +// 蒙版 +$uni-mask: rgba($color: #000000, $alpha: 0.4); diff --git a/uniapp/uni_modules/uni-search-bar/changelog.md b/uniapp/uni_modules/uni-search-bar/changelog.md new file mode 100644 index 0000000..b41fdd3 --- /dev/null +++ b/uniapp/uni_modules/uni-search-bar/changelog.md @@ -0,0 +1,33 @@ +## 1.2.3(2022-05-24) +- 新增 readonly 属性,组件只读 +## 1.2.2(2022-05-06) +- 修复 vue3 input 事件不生效的bug +## 1.2.1(2022-05-06) +- 修复 多余代码导致的bug +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-search-bar](https://uniapp.dcloud.io/component/uniui/uni-search-bar) +## 1.1.2(2021-08-30) +- 修复 value 属性与 modelValue 属性不兼容的Bug +## 1.1.1(2021-08-24) +- 新增 支持国际化 +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.9(2021-05-12) +- 新增 项目示例地址 +## 1.0.8(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.7(2021-04-15) +- uni-ui 新增 uni-search-bar 的 focus 事件 + +## 1.0.6(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.5(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 支持双向绑定 +- 更改 input 事件的返回值,e={value:Number} --> e=value +- 新增 支持图标插槽 +- 新增 支持 clear、blur 事件 +- 新增 支持 focus 属性 +- 去掉组件背景色 diff --git a/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json b/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json new file mode 100644 index 0000000..dd083a5 --- /dev/null +++ b/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/en.json @@ -0,0 +1,4 @@ +{ + "uni-search-bar.cancel": "cancel", + "uni-search-bar.placeholder": "Search enter content" +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js b/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js new file mode 100644 index 0000000..de7509c --- /dev/null +++ b/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/index.js @@ -0,0 +1,8 @@ +import en from './en.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json b/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json new file mode 100644 index 0000000..d4e5c12 --- /dev/null +++ b/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hans.json @@ -0,0 +1,4 @@ +{ + "uni-search-bar.cancel": "cancel", + "uni-search-bar.placeholder": "请输入搜索内容" +} diff --git a/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json b/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json new file mode 100644 index 0000000..318b6ef --- /dev/null +++ b/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/i18n/zh-Hant.json @@ -0,0 +1,4 @@ +{ + "uni-search-bar.cancel": "cancel", + "uni-search-bar.placeholder": "請輸入搜索內容" +} diff --git a/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue b/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue new file mode 100644 index 0000000..5a518a8 --- /dev/null +++ b/uniapp/uni_modules/uni-search-bar/components/uni-search-bar/uni-search-bar.vue @@ -0,0 +1,298 @@ + + + + + diff --git a/uniapp/uni_modules/uni-search-bar/package.json b/uniapp/uni_modules/uni-search-bar/package.json new file mode 100644 index 0000000..9352c57 --- /dev/null +++ b/uniapp/uni_modules/uni-search-bar/package.json @@ -0,0 +1,89 @@ +{ + "id": "uni-search-bar", + "displayName": "uni-search-bar 搜索栏", + "version": "1.2.3", + "description": "搜索栏组件,通常用于搜索商品、文章等", + "keywords": [ + "uni-ui", + "uniui", + "搜索框", + "搜索栏" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-search-bar/readme.md b/uniapp/uni_modules/uni-search-bar/readme.md new file mode 100644 index 0000000..253092f --- /dev/null +++ b/uniapp/uni_modules/uni-search-bar/readme.md @@ -0,0 +1,14 @@ + + +## SearchBar 搜索栏 + +> **组件名:uni-search-bar** +> 代码块: `uSearchBar` + + +搜索栏组件 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-search-bar) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/uniapp/uni_modules/uni-section/changelog.md b/uniapp/uni_modules/uni-section/changelog.md new file mode 100644 index 0000000..738f2b3 --- /dev/null +++ b/uniapp/uni_modules/uni-section/changelog.md @@ -0,0 +1,2 @@ +## 0.0.1(2022-07-22) +- 初始化 diff --git a/uniapp/uni_modules/uni-section/components/uni-section/uni-section.vue b/uniapp/uni_modules/uni-section/components/uni-section/uni-section.vue new file mode 100644 index 0000000..9a52e0b --- /dev/null +++ b/uniapp/uni_modules/uni-section/components/uni-section/uni-section.vue @@ -0,0 +1,167 @@ + + + + diff --git a/uniapp/uni_modules/uni-section/package.json b/uniapp/uni_modules/uni-section/package.json new file mode 100644 index 0000000..0a31fb5 --- /dev/null +++ b/uniapp/uni_modules/uni-section/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-section", + "displayName": "uni-section 标题栏", + "version": "0.0.1", + "description": "标题栏组件", + "keywords": [ + "uni-ui", + "uniui", + "标题栏" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-section/readme.md b/uniapp/uni_modules/uni-section/readme.md new file mode 100644 index 0000000..d47faab --- /dev/null +++ b/uniapp/uni_modules/uni-section/readme.md @@ -0,0 +1,8 @@ +## Section 标题栏 +> **组件名:uni-section** +> 代码块: `uSection` + +uni-section 组件主要用于文章、列表详情等标题展示 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-section) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/uniapp/uni_modules/uni-segmented-control/changelog.md b/uniapp/uni_modules/uni-segmented-control/changelog.md new file mode 100644 index 0000000..a44385d --- /dev/null +++ b/uniapp/uni_modules/uni-segmented-control/changelog.md @@ -0,0 +1,9 @@ +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-segmented-control](https://uniapp.dcloud.io/component/uniui/uni-segmented-control) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.5(2021-05-12) +- 新增 项目示例地址 +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue b/uniapp/uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue new file mode 100644 index 0000000..e8bb3e5 --- /dev/null +++ b/uniapp/uni_modules/uni-segmented-control/components/uni-segmented-control/uni-segmented-control.vue @@ -0,0 +1,145 @@ + + + + + diff --git a/uniapp/uni_modules/uni-segmented-control/package.json b/uniapp/uni_modules/uni-segmented-control/package.json new file mode 100644 index 0000000..6cae41d --- /dev/null +++ b/uniapp/uni_modules/uni-segmented-control/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-segmented-control", + "displayName": "uni-segmented-control 分段器", + "version": "1.2.0", + "description": "分段器由至少 2 个分段控件组成,用作不同视图的显示", + "keywords": [ + "uni-ui", + "uniui", + "分段器", + "segement", + "顶部选择" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-segmented-control/readme.md b/uniapp/uni_modules/uni-segmented-control/readme.md new file mode 100644 index 0000000..3527b03 --- /dev/null +++ b/uniapp/uni_modules/uni-segmented-control/readme.md @@ -0,0 +1,13 @@ + + +## SegmentedControl 分段器 +> **组件名:uni-segmented-control** +> 代码块: `uSegmentedControl` + + +用作不同视图的显示 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-segmented-control) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/uniapp/uni_modules/uni-steps/changelog.md b/uniapp/uni_modules/uni-steps/changelog.md new file mode 100644 index 0000000..cb9d367 --- /dev/null +++ b/uniapp/uni_modules/uni-steps/changelog.md @@ -0,0 +1,16 @@ +## 1.1.1(2021-11-22) +- 修复 vue3中某些scss变量无法找到的问题 +## 1.1.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-steps](https://uniapp.dcloud.io/component/uniui/uni-steps) +## 1.0.8(2021-05-12) +- 新增 项目示例地址 +## 1.0.7(2021-05-06) +- 修复 uni-steps 横向布局时,多行文字高度不合理的 bug +## 1.0.6(2021-04-21) +- 优化 添加依赖 uni-icons, 导入后自动下载依赖 +## 1.0.5(2021-02-05) +- 优化 组件引用关系,通过uni_modules引用组件 + +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-steps/components/uni-steps/uni-steps.vue b/uniapp/uni_modules/uni-steps/components/uni-steps/uni-steps.vue new file mode 100644 index 0000000..a6c8f28 --- /dev/null +++ b/uniapp/uni_modules/uni-steps/components/uni-steps/uni-steps.vue @@ -0,0 +1,269 @@ + + + + + diff --git a/uniapp/uni_modules/uni-steps/package.json b/uniapp/uni_modules/uni-steps/package.json new file mode 100644 index 0000000..c687b40 --- /dev/null +++ b/uniapp/uni_modules/uni-steps/package.json @@ -0,0 +1,89 @@ +{ + "id": "uni-steps", + "displayName": "uni-steps 步骤条", + "version": "1.1.1", + "description": "步骤条组件,提供横向和纵向两种布局格式。", + "keywords": [ + "uni-ui", + "uniui", + "步骤条", + "时间轴" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": [ + "uni-scss", + "uni-icons" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-steps/readme.md b/uniapp/uni_modules/uni-steps/readme.md new file mode 100644 index 0000000..da7a4bf --- /dev/null +++ b/uniapp/uni_modules/uni-steps/readme.md @@ -0,0 +1,13 @@ + + +## Steps 步骤条 +> **组件名:uni-steps** +> 代码块: `uSteps` + + +步骤条,常用于显示进度 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-steps) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/uniapp/uni_modules/uni-swipe-action/changelog.md b/uniapp/uni_modules/uni-swipe-action/changelog.md new file mode 100644 index 0000000..c916427 --- /dev/null +++ b/uniapp/uni_modules/uni-swipe-action/changelog.md @@ -0,0 +1,43 @@ +## 1.3.8(2023-04-13) +- 修复`uni-swipe-action`和`uni-swipe-action-item`不同时使用导致 closeOther 方法报错的 bug +## 1.3.7(2022-06-06) +- 修复 vue3 下使用组件不能正常运行的Bug +## 1.3.6(2022-05-31) +- 修复 h5端点击click触发两次的Bug +## 1.3.5(2022-05-23) +- 修复 isPC 找不到的Bug +## 1.3.4(2022-05-19) +- 修复 在 nvue 下 disabled 失效的bug +## 1.3.3(2022-03-31) +- 修复 按钮字体大小不能设置的bug +## 1.3.2(2022-03-16) +- 修复 h5和app端下报el错误的bug +## 1.3.1(2022-03-07) +- 修复 HBuilderX 1.4.X 版本中,h5和app端下报错的bug +## 1.3.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-swipe-action](https://uniapp.dcloud.io/component/uniui/uni-swipe-action) +## 1.2.4(2021-08-20) +- 优化 close-all 方法 +## 1.2.3(2021-08-20) +- 新增 close-all 方法,关闭所有已打开的组件 +## 1.2.2(2021-08-17) +- 新增 resize() 方法,在非微信小程序、h5、app-vue端出现不能滑动的问题的时候,重置组件 +- 修复 app 端偶尔出现类似 Page[x][-x,xx;-x,xx,x,x-x] 的问题 +- 优化 微信小程序、h5、app-vue 滑动逻辑,避免出现动态新增组件后不能滑动的问题 +## 1.2.1(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +- 修复 跨页面修改组件数据 ,导致不能滑动的问题 +## 1.1.10(2021-06-17) +- 修复 按钮点击执行两次的bug +## 1.1.9(2021-05-12) +- 新增 项目示例地址 +## 1.1.8(2021-03-26) +- 修复 微信小程序 nv_navigator is not defined 报错的bug +## 1.1.7(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 左侧滑动 +- 新增 插槽使用方式 +- 新增 threshold 属性,可以控制滑动缺省值 +- 优化 长列表滚动性能 +- 修复 滚动页面时触发组件滑动的Bug diff --git a/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/bindingx.js b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/bindingx.js new file mode 100644 index 0000000..707e432 --- /dev/null +++ b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/bindingx.js @@ -0,0 +1,302 @@ +let bindIngXMixins = {} + +// #ifdef APP-NVUE +const BindingX = uni.requireNativePlugin('bindingx'); +const dom = uni.requireNativePlugin('dom'); +const animation = uni.requireNativePlugin('animation'); + +bindIngXMixins = { + data() { + return {} + }, + + watch: { + show(newVal) { + if (this.autoClose) return + if (this.stop) return + this.stop = true + if (newVal) { + this.open(newVal) + } else { + this.close() + } + }, + leftOptions() { + this.getSelectorQuery() + this.init() + }, + rightOptions(newVal) { + this.init() + } + }, + created() { + this.swipeaction = this.getSwipeAction() + if (this.swipeaction && Array.isArray(this.swipeaction.children)) { + this.swipeaction.children.push(this) + } + }, + mounted() { + this.box = this.getEl(this.$refs['selector-box--hock']) + this.selector = this.getEl(this.$refs['selector-content--hock']); + this.leftButton = this.getEl(this.$refs['selector-left-button--hock']); + this.rightButton = this.getEl(this.$refs['selector-right-button--hock']); + this.init() + }, + // beforeDestroy() { + // this.swipeaction.children.forEach((item, index) => { + // if (item === this) { + // this.swipeaction.children.splice(index, 1) + // } + // }) + // }, + methods: { + init() { + this.$nextTick(() => { + this.x = 0 + this.button = { + show: false + } + setTimeout(() => { + this.getSelectorQuery() + }, 200) + }) + }, + onClick(index, item, position) { + this.$emit('click', { + content: item, + index, + position + }) + }, + touchstart(e) { + // fix by mehaotian 禁止滑动 + if (this.disabled) return + // 每次只触发一次,避免多次监听造成闪烁 + if (this.stop) return + this.stop = true + if (this.autoClose && this.swipeaction) { + this.swipeaction.closeOther(this) + } + + const leftWidth = this.button.left.width + const rightWidth = this.button.right.width + let expression = this.range(this.x, -rightWidth, leftWidth) + let leftExpression = this.range(this.x - leftWidth, -leftWidth, 0) + let rightExpression = this.range(this.x + rightWidth, 0, rightWidth) + + this.eventpan = BindingX.bind({ + anchor: this.box, + eventType: 'pan', + props: [{ + element: this.selector, + property: 'transform.translateX', + expression + }, { + element: this.leftButton, + property: 'transform.translateX', + expression: leftExpression + }, { + element: this.rightButton, + property: 'transform.translateX', + expression: rightExpression + }, ] + }, (e) => { + // nope + if (e.state === 'end') { + this.x = e.deltaX + this.x; + this.isclick = true + this.bindTiming(e.deltaX) + } + }); + }, + touchend(e) { + if (this.isopen !== 'none' && !this.isclick) { + this.open('none') + } + }, + bindTiming(x) { + const left = this.x + const leftWidth = this.button.left.width + const rightWidth = this.button.right.width + const threshold = this.threshold + if (!this.isopen || this.isopen === 'none') { + if (left > threshold) { + this.open('left') + } else if (left < -threshold) { + this.open('right') + } else { + this.open('none') + } + } else { + if ((x > -leftWidth && x < 0) || x > rightWidth) { + if ((x > -threshold && x < 0) || (x - rightWidth > threshold)) { + this.open('left') + } else { + this.open('none') + } + } else { + if ((x < threshold && x > 0) || (x + leftWidth < -threshold)) { + this.open('right') + } else { + this.open('none') + } + } + } + }, + + /** + * 移动范围 + * @param {Object} num + * @param {Object} mix + * @param {Object} max + */ + range(num, mix, max) { + return `min(max(x+${num}, ${mix}), ${max})` + }, + + /** + * 开启swipe + */ + open(type) { + this.animation(type) + }, + + /** + * 关闭swipe + */ + close() { + this.animation('none') + }, + + /** + * 开启关闭动画 + * @param {Object} type + */ + animation(type) { + const time = 300 + const leftWidth = this.button.left.width + const rightWidth = this.button.right.width + if (this.eventpan && this.eventpan.token) { + BindingX.unbind({ + token: this.eventpan.token, + eventType: 'pan' + }) + } + + switch (type) { + case 'left': + Promise.all([ + this.move(this.selector, leftWidth), + this.move(this.leftButton, 0), + this.move(this.rightButton, rightWidth * 2) + ]).then(() => { + this.setEmit(leftWidth, type) + }) + break + case 'right': + Promise.all([ + this.move(this.selector, -rightWidth), + this.move(this.leftButton, -leftWidth * 2), + this.move(this.rightButton, 0) + ]).then(() => { + this.setEmit(-rightWidth, type) + }) + break + default: + Promise.all([ + this.move(this.selector, 0), + this.move(this.leftButton, -leftWidth), + this.move(this.rightButton, rightWidth) + ]).then(() => { + this.setEmit(0, type) + }) + + } + }, + setEmit(x, type) { + const leftWidth = this.button.left.width + const rightWidth = this.button.right.width + this.isopen = this.isopen || 'none' + this.stop = false + this.isclick = false + // 只有状态不一致才会返回结果 + if (this.isopen !== type && this.x !== x) { + if (type === 'left' && leftWidth > 0) { + this.$emit('change', 'left') + } + if (type === 'right' && rightWidth > 0) { + this.$emit('change', 'right') + } + if (type === 'none') { + this.$emit('change', 'none') + } + } + this.x = x + this.isopen = type + }, + move(ref, value) { + return new Promise((resolve, reject) => { + animation.transition(ref, { + styles: { + transform: `translateX(${value})`, + }, + duration: 150, //ms + timingFunction: 'linear', + needLayout: false, + delay: 0 //ms + }, function(res) { + resolve(res) + }) + }) + + }, + + /** + * 获取ref + * @param {Object} el + */ + getEl(el) { + return el.ref + }, + /** + * 获取节点信息 + */ + getSelectorQuery() { + Promise.all([ + this.getDom('left'), + this.getDom('right'), + ]).then((data) => { + let show = 'none' + if (this.autoClose) { + show = 'none' + } else { + show = this.show + } + + if (show === 'none') { + // this.close() + } else { + this.open(show) + } + + }) + + }, + getDom(str) { + return new Promise((resolve, reject) => { + dom.getComponentRect(this.$refs[`selector-${str}-button--hock`], (data) => { + if (data) { + this.button[str] = data.size + resolve(data) + } else { + reject() + } + }) + }) + } + } +} + +// #endif + +export default bindIngXMixins diff --git a/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/isPC.js b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/isPC.js new file mode 100644 index 0000000..917cb48 --- /dev/null +++ b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/isPC.js @@ -0,0 +1,12 @@ +export function isPC() { + var userAgentInfo = navigator.userAgent; + var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; + var flag = true; + for (let v = 0; v < Agents.length - 1; v++) { + if (userAgentInfo.indexOf(Agents[v]) > 0) { + flag = false; + break; + } + } + return flag; +} diff --git a/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpalipay.js b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpalipay.js new file mode 100644 index 0000000..35c796b --- /dev/null +++ b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpalipay.js @@ -0,0 +1,195 @@ +export default { + data() { + return { + x: 0, + transition: false, + width: 0, + viewWidth: 0, + swipeShow: 0 + } + }, + watch: { + show(newVal) { + if (this.autoClose) return + if (newVal && newVal !== 'none') { + this.transition = true + this.open(newVal) + } else { + this.close() + } + } + }, + created() { + this.swipeaction = this.getSwipeAction() + if (this.swipeaction && Array.isArray(this.swipeaction.children)) { + this.swipeaction.children.push(this) + } + }, + mounted() { + this.isopen = false + setTimeout(() => { + this.getQuerySelect() + }, 50) + }, + methods: { + appTouchStart(e) { + const { + clientX + } = e.changedTouches[0] + this.clientX = clientX + this.timestamp = new Date().getTime() + }, + appTouchEnd(e, index, item, position) { + const { + clientX + } = e.changedTouches[0] + // fixed by xxxx 模拟点击事件,解决 ios 13 点击区域错位的问题 + let diff = Math.abs(this.clientX - clientX) + let time = (new Date().getTime()) - this.timestamp + if (diff < 40 && time < 300) { + this.$emit('click', { + content: item, + index, + position + }) + } + }, + /** + * 移动触发 + * @param {Object} e + */ + onChange(e) { + this.moveX = e.detail.x + this.isclose = false + }, + touchstart(e) { + this.transition = false + this.isclose = true + if (this.autoClose && this.swipeaction) { + this.swipeaction.closeOther(this) + } + }, + touchmove(e) {}, + touchend(e) { + // 0的位置什么都不执行 + if (this.isclose && this.isopen === 'none') return + if (this.isclose && this.isopen !== 'none') { + this.transition = true + this.close() + } else { + this.move(this.moveX + this.leftWidth) + } + }, + + /** + * 移动 + * @param {Object} moveX + */ + move(moveX) { + // 打开关闭的处理逻辑不太一样 + this.transition = true + // 未打开状态 + if (!this.isopen || this.isopen === 'none') { + if (moveX > this.threshold) { + this.open('left') + } else if (moveX < -this.threshold) { + this.open('right') + } else { + this.close() + } + } else { + if (moveX < 0 && moveX < this.rightWidth) { + const rightX = this.rightWidth + moveX + if (rightX < this.threshold) { + this.open('right') + } else { + this.close() + } + } else if (moveX > 0 && moveX < this.leftWidth) { + const leftX = this.leftWidth - moveX + if (leftX < this.threshold) { + this.open('left') + } else { + this.close() + } + } + + } + + }, + + /** + * 打开 + */ + open(type) { + this.x = this.moveX + this.animation(type) + }, + + /** + * 关闭 + */ + close() { + this.x = this.moveX + // TODO 解决 x 值不更新的问题,所以会多触发一次 nextTick ,待优化 + this.$nextTick(() => { + this.x = -this.leftWidth + if (this.isopen !== 'none') { + this.$emit('change', 'none') + } + this.isopen = 'none' + }) + }, + + /** + * 执行结束动画 + * @param {Object} type + */ + animation(type) { + this.$nextTick(() => { + if (type === 'left') { + this.x = 0 + } else { + this.x = -this.rightWidth - this.leftWidth + } + + if (this.isopen !== type) { + this.$emit('change', type) + } + this.isopen = type + }) + + }, + getSlide(x) {}, + getQuerySelect() { + const query = uni.createSelectorQuery().in(this); + query.selectAll('.movable-view--hock').boundingClientRect(data => { + this.leftWidth = data[1].width + this.rightWidth = data[2].width + this.width = data[0].width + this.viewWidth = this.width + this.rightWidth + this.leftWidth + if (this.leftWidth === 0) { + // TODO 疑似bug ,初始化的时候如果x 是0,会导致移动位置错误,所以让元素超出一点 + this.x = -0.1 + } else { + this.x = -this.leftWidth + } + this.moveX = this.x + this.$nextTick(() => { + this.swipeShow = 1 + }) + + if (!this.buttonWidth) { + this.disabledView = true + } + + if (this.autoClose) return + if (this.show !== 'none') { + this.transition = true + this.open(this.shows) + } + }).exec(); + + } + } +} diff --git a/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpother.js b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpother.js new file mode 100644 index 0000000..d389bce --- /dev/null +++ b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpother.js @@ -0,0 +1,260 @@ +let otherMixins = {} + +// #ifndef APP-PLUS|| MP-WEIXIN || H5 +const MIN_DISTANCE = 10; +otherMixins = { + data() { + // TODO 随机生生元素ID,解决百度小程序获取同一个元素位置信息的bug + const elClass = `Uni_${Math.ceil(Math.random() * 10e5).toString(36)}` + return { + uniShow: false, + left: 0, + buttonShow: 'none', + ani: false, + moveLeft: '', + elClass + } + }, + watch: { + show(newVal) { + if (this.autoClose) return + this.openState(newVal) + }, + left() { + this.moveLeft = `translateX(${this.left}px)` + }, + buttonShow(newVal) { + if (this.autoClose) return + this.openState(newVal) + }, + leftOptions() { + this.init() + }, + rightOptions() { + this.init() + } + }, + mounted() { + this.swipeaction = this.getSwipeAction() + if (this.swipeaction && Array.isArray(this.swipeaction.children)) { + this.swipeaction.children.push(this) + } + this.init() + }, + methods: { + init() { + clearTimeout(this.timer) + this.timer = setTimeout(() => { + this.getSelectorQuery() + }, 100) + // 移动距离 + this.left = 0 + this.x = 0 + }, + + closeSwipe(e) { + if (this.autoClose && this.swipeaction) { + this.swipeaction.closeOther(this) + } + }, + appTouchStart(e) { + const { + clientX + } = e.changedTouches[0] + this.clientX = clientX + this.timestamp = new Date().getTime() + }, + appTouchEnd(e, index, item, position) { + const { + clientX + } = e.changedTouches[0] + // fixed by xxxx 模拟点击事件,解决 ios 13 点击区域错位的问题 + let diff = Math.abs(this.clientX - clientX) + let time = (new Date().getTime()) - this.timestamp + if (diff < 40 && time < 300) { + this.$emit('click', { + content: item, + index, + position + }) + } + }, + touchstart(e) { + if (this.disabled) return + this.ani = false + this.x = this.left || 0 + this.stopTouchStart(e) + this.autoClose && this.closeSwipe() + }, + touchmove(e) { + if (this.disabled) return + // 是否可以滑动页面 + this.stopTouchMove(e); + if (this.direction !== 'horizontal') { + return; + } + this.move(this.x + this.deltaX) + return false + }, + touchend() { + if (this.disabled) return + this.moveDirection(this.left) + }, + /** + * 设置移动距离 + * @param {Object} value + */ + move(value) { + value = value || 0 + const leftWidth = this.leftWidth + const rightWidth = this.rightWidth + // 获取可滑动范围 + this.left = this.range(value, -rightWidth, leftWidth); + }, + + /** + * 获取范围 + * @param {Object} num + * @param {Object} min + * @param {Object} max + */ + range(num, min, max) { + return Math.min(Math.max(num, min), max); + }, + /** + * 移动方向判断 + * @param {Object} left + * @param {Object} value + */ + moveDirection(left) { + const threshold = this.threshold + const isopen = this.isopen || 'none' + const leftWidth = this.leftWidth + const rightWidth = this.rightWidth + if (this.deltaX === 0) { + this.openState('none') + return + } + if ((isopen === 'none' && rightWidth > 0 && -left > threshold) || (isopen !== 'none' && rightWidth > + 0 && rightWidth + + left < threshold)) { + // right + this.openState('right') + } else if ((isopen === 'none' && leftWidth > 0 && left > threshold) || (isopen !== 'none' && leftWidth > + 0 && + leftWidth - left < threshold)) { + // left + this.openState('left') + } else { + // default + this.openState('none') + } + }, + + /** + * 开启状态 + * @param {Boolean} type + */ + openState(type) { + const leftWidth = this.leftWidth + const rightWidth = this.rightWidth + let left = '' + this.isopen = this.isopen ? this.isopen : 'none' + switch (type) { + case "left": + left = leftWidth + break + case "right": + left = -rightWidth + break + default: + left = 0 + } + + + if (this.isopen !== type) { + this.throttle = true + this.$emit('change', type) + } + + this.isopen = type + // 添加动画类 + this.ani = true + this.$nextTick(() => { + this.move(left) + }) + // 设置最终移动位置,理论上只要进入到这个函数,肯定是要打开的 + }, + close() { + this.openState('none') + }, + getDirection(x, y) { + if (x > y && x > MIN_DISTANCE) { + return 'horizontal'; + } + if (y > x && y > MIN_DISTANCE) { + return 'vertical'; + } + return ''; + }, + + /** + * 重置滑动状态 + * @param {Object} event + */ + resetTouchStatus() { + this.direction = ''; + this.deltaX = 0; + this.deltaY = 0; + this.offsetX = 0; + this.offsetY = 0; + }, + + /** + * 设置滑动开始位置 + * @param {Object} event + */ + stopTouchStart(event) { + this.resetTouchStatus(); + const touch = event.touches[0]; + this.startX = touch.clientX; + this.startY = touch.clientY; + }, + + /** + * 滑动中,是否禁止打开 + * @param {Object} event + */ + stopTouchMove(event) { + const touch = event.touches[0]; + this.deltaX = touch.clientX - this.startX; + this.deltaY = touch.clientY - this.startY; + this.offsetX = Math.abs(this.deltaX); + this.offsetY = Math.abs(this.deltaY); + this.direction = this.direction || this.getDirection(this.offsetX, this.offsetY); + }, + + getSelectorQuery() { + const views = uni.createSelectorQuery().in(this) + views + .selectAll('.' + this.elClass) + .boundingClientRect(data => { + if (data.length === 0) return + let show = 'none' + if (this.autoClose) { + show = 'none' + } else { + show = this.show + } + this.leftWidth = data[0].width || 0 + this.rightWidth = data[1].width || 0 + this.buttonShow = show + }) + .exec() + } + } +} + +// #endif + +export default otherMixins diff --git a/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpwxs.js b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpwxs.js new file mode 100644 index 0000000..08de1c9 --- /dev/null +++ b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/mpwxs.js @@ -0,0 +1,84 @@ +let mpMixins = {} +let is_pc = null +// #ifdef H5 +import { + isPC +} from "./isPC" +is_pc = isPC() +// #endif +// #ifdef APP-VUE|| MP-WEIXIN || H5 + +mpMixins = { + data() { + return { + is_show: 'none' + } + }, + watch: { + show(newVal) { + this.is_show = this.show + } + }, + created() { + this.swipeaction = this.getSwipeAction() + if (this.swipeaction && Array.isArray(this.swipeaction.children)) { + this.swipeaction.children.push(this) + } + }, + mounted() { + this.is_show = this.show + }, + methods: { + // wxs 中调用 + closeSwipe(e) { + if (this.autoClose && this.swipeaction) { + this.swipeaction.closeOther(this) + } + }, + + change(e) { + this.$emit('change', e.open) + if (this.is_show !== e.open) { + this.is_show = e.open + } + }, + + appTouchStart(e) { + if (is_pc) return + const { + clientX + } = e.changedTouches[0] + this.clientX = clientX + this.timestamp = new Date().getTime() + }, + appTouchEnd(e, index, item, position) { + if (is_pc) return + const { + clientX + } = e.changedTouches[0] + // fixed by xxxx 模拟点击事件,解决 ios 13 点击区域错位的问题 + let diff = Math.abs(this.clientX - clientX) + let time = (new Date().getTime()) - this.timestamp + if (diff < 40 && time < 300) { + this.$emit('click', { + content: item, + index, + position + }) + } + }, + onClickForPC(index, item, position) { + if (!is_pc) return + // #ifdef H5 + this.$emit('click', { + content: item, + index, + position + }) + // #endif + } + } +} + +// #endif +export default mpMixins diff --git a/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/render.js b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/render.js new file mode 100644 index 0000000..78f0ec6 --- /dev/null +++ b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/render.js @@ -0,0 +1,270 @@ +const MIN_DISTANCE = 10; +export default { + showWatch(newVal, oldVal, ownerInstance, instance, self) { + var state = self.state + var $el = ownerInstance.$el || ownerInstance.$vm && ownerInstance.$vm.$el + if (!$el) return + this.getDom(instance, ownerInstance, self) + if (newVal && newVal !== 'none') { + this.openState(newVal, instance, ownerInstance, self) + return + } + + if (state.left) { + this.openState('none', instance, ownerInstance, self) + } + this.resetTouchStatus(instance, self) + }, + + /** + * 开始触摸操作 + * @param {Object} e + * @param {Object} ins + */ + touchstart(e, ownerInstance, self) { + let instance = e.instance; + let disabled = instance.getDataset().disabled + let state = self.state; + this.getDom(instance, ownerInstance, self) + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = this.getDisabledType(disabled) + if (disabled) return + // 开始触摸时移除动画类 + instance.requestAnimationFrame(function() { + instance.removeClass('ani'); + ownerInstance.callMethod('closeSwipe'); + }) + + // 记录上次的位置 + state.x = state.left || 0 + // 计算滑动开始位置 + this.stopTouchStart(e, ownerInstance, self) + }, + + /** + * 开始滑动操作 + * @param {Object} e + * @param {Object} ownerInstance + */ + touchmove(e, ownerInstance, self) { + let instance = e.instance; + // 删除之后已经那不到实例了 + if (!instance) return; + let disabled = instance.getDataset().disabled + let state = self.state + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = this.getDisabledType(disabled) + if (disabled) return + // 是否可以滑动页面 + this.stopTouchMove(e, self); + if (state.direction !== 'horizontal') { + return; + } + if (e.preventDefault) { + // 阻止页面滚动 + e.preventDefault() + } + let x = state.x + state.deltaX + this.move(x, instance, ownerInstance, self) + }, + + /** + * 结束触摸操作 + * @param {Object} e + * @param {Object} ownerInstance + */ + touchend(e, ownerInstance, self) { + let instance = e.instance; + let disabled = instance.getDataset().disabled + let state = self.state + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = this.getDisabledType(disabled) + + if (disabled) return + // 滑动过程中触摸结束,通过阙值判断是开启还是关闭 + // fixed by mehaotian 定时器解决点击按钮,touchend 触发比 click 事件时机早的问题 ,主要是 ios13 + this.moveDirection(state.left, instance, ownerInstance, self) + + }, + + /** + * 设置移动距离 + * @param {Object} value + * @param {Object} instance + * @param {Object} ownerInstance + */ + move(value, instance, ownerInstance, self) { + value = value || 0 + let state = self.state + let leftWidth = state.leftWidth + let rightWidth = state.rightWidth + // 获取可滑动范围 + state.left = this.range(value, -rightWidth, leftWidth); + instance.requestAnimationFrame(function() { + instance.setStyle({ + transform: 'translateX(' + state.left + 'px)', + '-webkit-transform': 'translateX(' + state.left + 'px)' + }) + }) + + }, + + /** + * 获取元素信息 + * @param {Object} instance + * @param {Object} ownerInstance + */ + getDom(instance, ownerInstance, self) { + var state = self.state + var $el = ownerInstance.$el || ownerInstance.$vm && ownerInstance.$vm.$el + var leftDom = $el.querySelector('.button-group--left') + var rightDom = $el.querySelector('.button-group--right') + + state.leftWidth = leftDom.offsetWidth || 0 + state.rightWidth = rightDom.offsetWidth || 0 + state.threshold = instance.getDataset().threshold + }, + + getDisabledType(value) { + return (typeof(value) === 'string' ? JSON.parse(value) : value) || false; + }, + + /** + * 获取范围 + * @param {Object} num + * @param {Object} min + * @param {Object} max + */ + range(num, min, max) { + return Math.min(Math.max(num, min), max); + }, + + + /** + * 移动方向判断 + * @param {Object} left + * @param {Object} value + * @param {Object} ownerInstance + * @param {Object} ins + */ + moveDirection(left, ins, ownerInstance, self) { + var state = self.state + var threshold = state.threshold + var position = state.position + var isopen = state.isopen || 'none' + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + if (state.deltaX === 0) { + this.openState('none', ins, ownerInstance, self) + return + } + if ((isopen === 'none' && rightWidth > 0 && -left > threshold) || (isopen !== 'none' && rightWidth > 0 && + rightWidth + + left < threshold)) { + // right + this.openState('right', ins, ownerInstance, self) + } else if ((isopen === 'none' && leftWidth > 0 && left > threshold) || (isopen !== 'none' && leftWidth > 0 && + leftWidth - left < threshold)) { + // left + this.openState('left', ins, ownerInstance, self) + } else { + // default + this.openState('none', ins, ownerInstance, self) + } + }, + + + /** + * 开启状态 + * @param {Boolean} type + * @param {Object} ins + * @param {Object} ownerInstance + */ + openState(type, ins, ownerInstance, self) { + let state = self.state + let leftWidth = state.leftWidth + let rightWidth = state.rightWidth + let left = '' + state.isopen = state.isopen ? state.isopen : 'none' + switch (type) { + case "left": + left = leftWidth + break + case "right": + left = -rightWidth + break + default: + left = 0 + } + + // && !state.throttle + + if (state.isopen !== type) { + state.throttle = true + ownerInstance.callMethod('change', { + open: type + }) + + } + + state.isopen = type + // 添加动画类 + ins.requestAnimationFrame(() => { + ins.addClass('ani'); + this.move(left, ins, ownerInstance, self) + }) + }, + + + getDirection(x, y) { + if (x > y && x > MIN_DISTANCE) { + return 'horizontal'; + } + if (y > x && y > MIN_DISTANCE) { + return 'vertical'; + } + return ''; + }, + + /** + * 重置滑动状态 + * @param {Object} event + */ + resetTouchStatus(instance, self) { + let state = self.state; + state.direction = ''; + state.deltaX = 0; + state.deltaY = 0; + state.offsetX = 0; + state.offsetY = 0; + }, + + /** + * 设置滑动开始位置 + * @param {Object} event + */ + stopTouchStart(event, ownerInstance, self) { + let instance = event.instance; + let state = self.state + this.resetTouchStatus(instance, self); + var touch = event.touches[0]; + state.startX = touch.clientX; + state.startY = touch.clientY; + }, + + /** + * 滑动中,是否禁止打开 + * @param {Object} event + */ + stopTouchMove(event, self) { + let instance = event.instance; + let state = self.state; + let touch = event.touches[0]; + + state.deltaX = touch.clientX - state.startX; + state.deltaY = touch.clientY - state.startY; + state.offsetY = Math.abs(state.deltaY); + state.offsetX = Math.abs(state.deltaX); + state.direction = state.direction || this.getDirection(state.offsetX, state.offsetY); + } +} diff --git a/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/uni-swipe-action-item.vue b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/uni-swipe-action-item.vue new file mode 100644 index 0000000..d79c297 --- /dev/null +++ b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/uni-swipe-action-item.vue @@ -0,0 +1,347 @@ + + + + + + diff --git a/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/wx.wxs b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/wx.wxs new file mode 100644 index 0000000..b394244 --- /dev/null +++ b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action-item/wx.wxs @@ -0,0 +1,341 @@ +var MIN_DISTANCE = 10; + +/** + * 判断当前是否为H5、app-vue + */ +var IS_HTML5 = false +if (typeof window === 'object') IS_HTML5 = true + +/** + * 监听页面内值的变化,主要用于动态开关swipe-action + * @param {Object} newValue + * @param {Object} oldValue + * @param {Object} ownerInstance + * @param {Object} instance + */ +function showWatch(newVal, oldVal, ownerInstance, instance) { + var state = instance.getState() + getDom(instance, ownerInstance) + if (newVal && newVal !== 'none') { + openState(newVal, instance, ownerInstance) + return + } + + if (state.left) { + openState('none', instance, ownerInstance) + } + resetTouchStatus(instance) +} + +/** + * 开始触摸操作 + * @param {Object} e + * @param {Object} ins + */ +function touchstart(e, ownerInstance) { + var instance = e.instance; + var disabled = instance.getDataset().disabled + var state = instance.getState(); + getDom(instance, ownerInstance) + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false; + if (disabled) return + // 开始触摸时移除动画类 + instance.requestAnimationFrame(function() { + instance.removeClass('ani'); + ownerInstance.callMethod('closeSwipe'); + }) + + // 记录上次的位置 + state.x = state.left || 0 + // 计算滑动开始位置 + stopTouchStart(e, ownerInstance) +} + +/** + * 开始滑动操作 + * @param {Object} e + * @param {Object} ownerInstance + */ +function touchmove(e, ownerInstance) { + var instance = e.instance; + var disabled = instance.getDataset().disabled + var state = instance.getState() + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false; + if (disabled) return + // 是否可以滑动页面 + stopTouchMove(e); + if (state.direction !== 'horizontal') { + return; + } + + if (e.preventDefault) { + // 阻止页面滚动 + e.preventDefault() + } + + move(state.x + state.deltaX, instance, ownerInstance) +} + +/** + * 结束触摸操作 + * @param {Object} e + * @param {Object} ownerInstance + */ +function touchend(e, ownerInstance) { + var instance = e.instance; + var disabled = instance.getDataset().disabled + var state = instance.getState() + // fix by mehaotian, TODO 兼容 app-vue 获取dataset为字符串 , h5 获取 为 undefined 的问题,待框架修复 + disabled = (typeof(disabled) === 'string' ? JSON.parse(disabled) : disabled) || false; + + if (disabled) return + // 滑动过程中触摸结束,通过阙值判断是开启还是关闭 + // fixed by mehaotian 定时器解决点击按钮,touchend 触发比 click 事件时机早的问题 ,主要是 ios13 + moveDirection(state.left, instance, ownerInstance) + +} + +/** + * 设置移动距离 + * @param {Object} value + * @param {Object} instance + * @param {Object} ownerInstance + */ +function move(value, instance, ownerInstance) { + value = value || 0 + var state = instance.getState() + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + // 获取可滑动范围 + state.left = range(value, -rightWidth, leftWidth); + instance.requestAnimationFrame(function() { + instance.setStyle({ + transform: 'translateX(' + state.left + 'px)', + '-webkit-transform': 'translateX(' + state.left + 'px)' + }) + }) + +} + +/** + * 获取元素信息 + * @param {Object} instance + * @param {Object} ownerInstance + */ +function getDom(instance, ownerInstance) { + var state = instance.getState() + var leftDom = ownerInstance.selectComponent('.button-group--left') + var rightDom = ownerInstance.selectComponent('.button-group--right') + var leftStyles = { + width: 0 + } + var rightStyles = { + width: 0 + } + leftStyles = leftDom.getBoundingClientRect() + rightStyles = rightDom.getBoundingClientRect() + + state.leftWidth = leftStyles.width || 0 + state.rightWidth = rightStyles.width || 0 + state.threshold = instance.getDataset().threshold +} + +/** + * 获取范围 + * @param {Object} num + * @param {Object} min + * @param {Object} max + */ +function range(num, min, max) { + return Math.min(Math.max(num, min), max); +} + + +/** + * 移动方向判断 + * @param {Object} left + * @param {Object} value + * @param {Object} ownerInstance + * @param {Object} ins + */ +function moveDirection(left, ins, ownerInstance) { + var state = ins.getState() + var threshold = state.threshold + var position = state.position + var isopen = state.isopen || 'none' + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + if (state.deltaX === 0) { + openState('none', ins, ownerInstance) + return + } + if ((isopen === 'none' && rightWidth > 0 && -left > threshold) || (isopen !== 'none' && rightWidth > 0 && + rightWidth + + left < threshold)) { + // right + openState('right', ins, ownerInstance) + } else if ((isopen === 'none' && leftWidth > 0 && left > threshold) || (isopen !== 'none' && leftWidth > 0 && + leftWidth - left < threshold)) { + // left + openState('left', ins, ownerInstance) + } else { + // default + openState('none', ins, ownerInstance) + } +} + + +/** + * 开启状态 + * @param {Boolean} type + * @param {Object} ins + * @param {Object} ownerInstance + */ +function openState(type, ins, ownerInstance) { + var state = ins.getState() + var leftWidth = state.leftWidth + var rightWidth = state.rightWidth + var left = '' + state.isopen = state.isopen ? state.isopen : 'none' + switch (type) { + case "left": + left = leftWidth + break + case "right": + left = -rightWidth + break + default: + left = 0 + } + + // && !state.throttle + + if (state.isopen !== type) { + state.throttle = true + ownerInstance.callMethod('change', { + open: type + }) + + } + + state.isopen = type + // 添加动画类 + ins.requestAnimationFrame(function() { + ins.addClass('ani'); + move(left, ins, ownerInstance) + }) + // 设置最终移动位置,理论上只要进入到这个函数,肯定是要打开的 +} + + +function getDirection(x, y) { + if (x > y && x > MIN_DISTANCE) { + return 'horizontal'; + } + if (y > x && y > MIN_DISTANCE) { + return 'vertical'; + } + return ''; +} + +/** + * 重置滑动状态 + * @param {Object} event + */ +function resetTouchStatus(instance) { + var state = instance.getState(); + state.direction = ''; + state.deltaX = 0; + state.deltaY = 0; + state.offsetX = 0; + state.offsetY = 0; +} + +/** + * 设置滑动开始位置 + * @param {Object} event + */ +function stopTouchStart(event) { + var instance = event.instance; + var state = instance.getState(); + resetTouchStatus(instance); + var touch = event.touches[0]; + if (IS_HTML5 && isPC()) { + touch = event; + } + state.startX = touch.clientX; + state.startY = touch.clientY; +} + +/** + * 滑动中,是否禁止打开 + * @param {Object} event + */ +function stopTouchMove(event) { + var instance = event.instance; + var state = instance.getState(); + var touch = event.touches[0]; + if (IS_HTML5 && isPC()) { + touch = event; + } + state.deltaX = touch.clientX - state.startX; + state.deltaY = touch.clientY - state.startY; + state.offsetY = Math.abs(state.deltaY); + state.offsetX = Math.abs(state.deltaX); + state.direction = state.direction || getDirection(state.offsetX, state.offsetY); +} + +function isPC() { + var userAgentInfo = navigator.userAgent; + var Agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]; + var flag = true; + for (var v = 0; v < Agents.length - 1; v++) { + if (userAgentInfo.indexOf(Agents[v]) > 0) { + flag = false; + break; + } + } + return flag; +} + +var movable = false + +function mousedown(e, ins) { + if (!IS_HTML5) return + if (!isPC()) return + touchstart(e, ins) + movable = true +} + +function mousemove(e, ins) { + if (!IS_HTML5) return + if (!isPC()) return + if (!movable) return + touchmove(e, ins) +} + +function mouseup(e, ins) { + if (!IS_HTML5) return + if (!isPC()) return + touchend(e, ins) + movable = false +} + +function mouseleave(e, ins) { + if (!IS_HTML5) return + if (!isPC()) return + movable = false +} + +module.exports = { + showWatch: showWatch, + touchstart: touchstart, + touchmove: touchmove, + touchend: touchend, + mousedown: mousedown, + mousemove: mousemove, + mouseup: mouseup, + mouseleave: mouseleave +} diff --git a/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue new file mode 100644 index 0000000..4971782 --- /dev/null +++ b/uniapp/uni_modules/uni-swipe-action/components/uni-swipe-action/uni-swipe-action.vue @@ -0,0 +1,60 @@ + + + + + diff --git a/uniapp/uni_modules/uni-swipe-action/package.json b/uniapp/uni_modules/uni-swipe-action/package.json new file mode 100644 index 0000000..47651a6 --- /dev/null +++ b/uniapp/uni_modules/uni-swipe-action/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-swipe-action", + "displayName": "uni-swipe-action 滑动操作", + "version": "1.3.8", + "description": "SwipeAction 滑动操作操作组件", + "keywords": [ + "", + "uni-ui", + "uniui", + "滑动删除", + "侧滑删除" + ], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "y", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uniapp/uni_modules/uni-swipe-action/readme.md b/uniapp/uni_modules/uni-swipe-action/readme.md new file mode 100644 index 0000000..93a5cac --- /dev/null +++ b/uniapp/uni_modules/uni-swipe-action/readme.md @@ -0,0 +1,11 @@ + + +## SwipeAction 滑动操作 +> **组件名:uni-swipe-action** +> 代码块: `uSwipeAction`、`uSwipeActionItem` + + +通过滑动触发选项的容器 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-swipe-action) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-swiper-dot/changelog.md b/uniapp/uni_modules/uni-swiper-dot/changelog.md new file mode 100644 index 0000000..85cf54d --- /dev/null +++ b/uniapp/uni_modules/uni-swiper-dot/changelog.md @@ -0,0 +1,12 @@ +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-swiper-dot](https://uniapp.dcloud.io/component/uniui/uni-swiper-dot) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.6(2021-05-12) +- 新增 示例地址 +- 修复 示例项目缺少组件的Bug +## 1.0.5(2021-02-05) +- 调整为uni_modules目录规范 +- 新增 clickItem 事件,支持指示点控制轮播 +- 新增 支持 pc 可用 diff --git a/uniapp/uni_modules/uni-swiper-dot/components/uni-swiper-dot/uni-swiper-dot.vue b/uniapp/uni_modules/uni-swiper-dot/components/uni-swiper-dot/uni-swiper-dot.vue new file mode 100644 index 0000000..46eb8c1 --- /dev/null +++ b/uniapp/uni_modules/uni-swiper-dot/components/uni-swiper-dot/uni-swiper-dot.vue @@ -0,0 +1,218 @@ + + + + + diff --git a/uniapp/uni_modules/uni-swiper-dot/package.json b/uniapp/uni_modules/uni-swiper-dot/package.json new file mode 100644 index 0000000..f2dd8d2 --- /dev/null +++ b/uniapp/uni_modules/uni-swiper-dot/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-swiper-dot", + "displayName": "uni-swiper-dot 轮播图指示点", + "version": "1.2.0", + "description": "自定义轮播图指示点组件", + "keywords": [ + "uni-ui", + "uniui", + "轮播图指示点", + "dot", + "swiper" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-swiper-dot/readme.md b/uniapp/uni_modules/uni-swiper-dot/readme.md new file mode 100644 index 0000000..7d397e2 --- /dev/null +++ b/uniapp/uni_modules/uni-swiper-dot/readme.md @@ -0,0 +1,11 @@ + + +## SwiperDot 轮播图指示点 +> **组件名:uni-swiper-dot** +> 代码块: `uSwiperDot` + + +自定义轮播图指示点 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-swiper-dot) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-table/changelog.md b/uniapp/uni_modules/uni-table/changelog.md new file mode 100644 index 0000000..9f87c67 --- /dev/null +++ b/uniapp/uni_modules/uni-table/changelog.md @@ -0,0 +1,27 @@ +## 1.2.3(2023-03-28) +- 修复 在vue3模式下可能会出现错误的问题 +## 1.2.2(2022-11-29) +- 优化 主题样式 +## 1.2.1(2022-06-06) +- 修复 微信小程序存在无使用组件的问题 +## 1.2.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-table](https://uniapp.dcloud.io/component/uniui/uni-table) +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-07-08) +- 新增 uni-th 支持 date 日期筛选范围 +## 1.0.6(2021-07-05) +- 新增 uni-th 支持 range 筛选范围 +## 1.0.5(2021-06-28) +- 新增 uni-th 筛选功能 +## 1.0.4(2021-05-12) +- 新增 示例地址 +- 修复 示例项目缺少组件的Bug +## 1.0.3(2021-04-16) +- 新增 sortable 属性,是否开启单列排序 +- 优化 表格多选逻辑 +## 1.0.2(2021-03-22) +- uni-tr 添加 disabled 属性,用于 type=selection 时,设置某行是否可由全选按钮控制 +## 1.0.1(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-table/components/uni-table/uni-table.vue b/uniapp/uni_modules/uni-table/components/uni-table/uni-table.vue new file mode 100644 index 0000000..21d9527 --- /dev/null +++ b/uniapp/uni_modules/uni-table/components/uni-table/uni-table.vue @@ -0,0 +1,455 @@ + + + + + diff --git a/uniapp/uni_modules/uni-table/components/uni-tbody/uni-tbody.vue b/uniapp/uni_modules/uni-table/components/uni-tbody/uni-tbody.vue new file mode 100644 index 0000000..fbe1bdc --- /dev/null +++ b/uniapp/uni_modules/uni-table/components/uni-tbody/uni-tbody.vue @@ -0,0 +1,29 @@ + + + + + diff --git a/uniapp/uni_modules/uni-table/components/uni-td/uni-td.vue b/uniapp/uni_modules/uni-table/components/uni-td/uni-td.vue new file mode 100644 index 0000000..9ce93e9 --- /dev/null +++ b/uniapp/uni_modules/uni-table/components/uni-td/uni-td.vue @@ -0,0 +1,90 @@ + + + + + diff --git a/uniapp/uni_modules/uni-table/components/uni-th/filter-dropdown.vue b/uniapp/uni_modules/uni-table/components/uni-th/filter-dropdown.vue new file mode 100644 index 0000000..df22a71 --- /dev/null +++ b/uniapp/uni_modules/uni-table/components/uni-th/filter-dropdown.vue @@ -0,0 +1,511 @@ + + + + + diff --git a/uniapp/uni_modules/uni-table/components/uni-th/uni-th.vue b/uniapp/uni_modules/uni-table/components/uni-th/uni-th.vue new file mode 100644 index 0000000..14889dd --- /dev/null +++ b/uniapp/uni_modules/uni-table/components/uni-th/uni-th.vue @@ -0,0 +1,285 @@ + + + + + diff --git a/uniapp/uni_modules/uni-table/components/uni-thead/uni-thead.vue b/uniapp/uni_modules/uni-table/components/uni-thead/uni-thead.vue new file mode 100644 index 0000000..0dd18cd --- /dev/null +++ b/uniapp/uni_modules/uni-table/components/uni-thead/uni-thead.vue @@ -0,0 +1,129 @@ + + + + + diff --git a/uniapp/uni_modules/uni-table/components/uni-tr/table-checkbox.vue b/uniapp/uni_modules/uni-table/components/uni-tr/table-checkbox.vue new file mode 100644 index 0000000..1089187 --- /dev/null +++ b/uniapp/uni_modules/uni-table/components/uni-tr/table-checkbox.vue @@ -0,0 +1,179 @@ + + + + + diff --git a/uniapp/uni_modules/uni-table/components/uni-tr/uni-tr.vue b/uniapp/uni_modules/uni-table/components/uni-tr/uni-tr.vue new file mode 100644 index 0000000..f9b9671 --- /dev/null +++ b/uniapp/uni_modules/uni-table/components/uni-tr/uni-tr.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/uniapp/uni_modules/uni-table/i18n/en.json b/uniapp/uni_modules/uni-table/i18n/en.json new file mode 100644 index 0000000..e32023c --- /dev/null +++ b/uniapp/uni_modules/uni-table/i18n/en.json @@ -0,0 +1,9 @@ +{ + "filter-dropdown.reset": "Reset", + "filter-dropdown.search": "Search", + "filter-dropdown.submit": "Submit", + "filter-dropdown.filter": "Filter", + "filter-dropdown.gt": "Greater or equal to", + "filter-dropdown.lt": "Less than or equal to", + "filter-dropdown.date": "Date" +} diff --git a/uniapp/uni_modules/uni-table/i18n/es.json b/uniapp/uni_modules/uni-table/i18n/es.json new file mode 100644 index 0000000..9afd04b --- /dev/null +++ b/uniapp/uni_modules/uni-table/i18n/es.json @@ -0,0 +1,9 @@ +{ + "filter-dropdown.reset": "Reiniciar", + "filter-dropdown.search": "Búsqueda", + "filter-dropdown.submit": "Entregar", + "filter-dropdown.filter": "Filtrar", + "filter-dropdown.gt": "Mayor o igual a", + "filter-dropdown.lt": "Menos que o igual a", + "filter-dropdown.date": "Fecha" +} diff --git a/uniapp/uni_modules/uni-table/i18n/fr.json b/uniapp/uni_modules/uni-table/i18n/fr.json new file mode 100644 index 0000000..b006237 --- /dev/null +++ b/uniapp/uni_modules/uni-table/i18n/fr.json @@ -0,0 +1,9 @@ +{ + "filter-dropdown.reset": "Réinitialiser", + "filter-dropdown.search": "Chercher", + "filter-dropdown.submit": "Soumettre", + "filter-dropdown.filter": "Filtre", + "filter-dropdown.gt": "Supérieur ou égal à", + "filter-dropdown.lt": "Inférieur ou égal à", + "filter-dropdown.date": "Date" +} diff --git a/uniapp/uni_modules/uni-table/i18n/index.js b/uniapp/uni_modules/uni-table/i18n/index.js new file mode 100644 index 0000000..2469dd0 --- /dev/null +++ b/uniapp/uni_modules/uni-table/i18n/index.js @@ -0,0 +1,12 @@ +import en from './en.json' +import es from './es.json' +import fr from './fr.json' +import zhHans from './zh-Hans.json' +import zhHant from './zh-Hant.json' +export default { + en, + es, + fr, + 'zh-Hans': zhHans, + 'zh-Hant': zhHant +} diff --git a/uniapp/uni_modules/uni-table/i18n/zh-Hans.json b/uniapp/uni_modules/uni-table/i18n/zh-Hans.json new file mode 100644 index 0000000..862af17 --- /dev/null +++ b/uniapp/uni_modules/uni-table/i18n/zh-Hans.json @@ -0,0 +1,9 @@ +{ + "filter-dropdown.reset": "重置", + "filter-dropdown.search": "搜索", + "filter-dropdown.submit": "确定", + "filter-dropdown.filter": "筛选", + "filter-dropdown.gt": "大于等于", + "filter-dropdown.lt": "小于等于", + "filter-dropdown.date": "日期范围" +} diff --git a/uniapp/uni_modules/uni-table/i18n/zh-Hant.json b/uniapp/uni_modules/uni-table/i18n/zh-Hant.json new file mode 100644 index 0000000..64f8061 --- /dev/null +++ b/uniapp/uni_modules/uni-table/i18n/zh-Hant.json @@ -0,0 +1,9 @@ +{ + "filter-dropdown.reset": "重置", + "filter-dropdown.search": "搜索", + "filter-dropdown.submit": "確定", + "filter-dropdown.filter": "篩選", + "filter-dropdown.gt": "大於等於", + "filter-dropdown.lt": "小於等於", + "filter-dropdown.date": "日期範圍" +} diff --git a/uniapp/uni_modules/uni-table/package.json b/uniapp/uni_modules/uni-table/package.json new file mode 100644 index 0000000..7c2f91c --- /dev/null +++ b/uniapp/uni_modules/uni-table/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-table", + "displayName": "uni-table 表格", + "version": "1.2.3", + "description": "表格组件,多用于展示多条结构类似的数据,如", + "keywords": [ + "uni-ui", + "uniui", + "table", + "表格" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, +"dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": ["uni-scss","uni-datetime-picker"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "n" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "n", + "QQ": "y" + }, + "快应用": { + "华为": "n", + "联盟": "n" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-table/readme.md b/uniapp/uni_modules/uni-table/readme.md new file mode 100644 index 0000000..bb08c79 --- /dev/null +++ b/uniapp/uni_modules/uni-table/readme.md @@ -0,0 +1,13 @@ + + +## Table 表单 +> 组件名:``uni-table``,代码块: `uTable`。 + +用于展示多条结构类似的数据 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-table) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + + + diff --git a/uniapp/uni_modules/uni-tag/changelog.md b/uniapp/uni_modules/uni-tag/changelog.md new file mode 100644 index 0000000..c0c5839 --- /dev/null +++ b/uniapp/uni_modules/uni-tag/changelog.md @@ -0,0 +1,21 @@ +## 2.1.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-tag](https://uniapp.dcloud.io/component/uniui/uni-tag) +## 2.0.0(2021-11-09) +- 新增 提供组件设计资源,组件样式调整 +- 移除 插槽 +- 移除 type 属性的 royal 选项 +## 1.1.1(2021-08-11) +- type 不是 default 时,size 为 small 字体大小显示不正确 +## 1.1.0(2021-07-30) +- 组件兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.0.7(2021-06-18) +- 修复 uni-tag 在字节跳动小程序上 css 类名编译错误的 bug +## 1.0.6(2021-06-04) +- 修复 未定义 sass 变量 "$uni-color-royal" 的bug +## 1.0.5(2021-05-10) +- 修复 royal 类型无效的bug +- 修复 uni-tag 宽度不自适应的bug +- 新增 uni-tag 支持属性 custom-style 自定义样式 +## 1.0.4(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-tag/components/uni-tag/uni-tag.vue b/uniapp/uni_modules/uni-tag/components/uni-tag/uni-tag.vue new file mode 100644 index 0000000..6378a0b --- /dev/null +++ b/uniapp/uni_modules/uni-tag/components/uni-tag/uni-tag.vue @@ -0,0 +1,252 @@ + + + + + diff --git a/uniapp/uni_modules/uni-tag/package.json b/uniapp/uni_modules/uni-tag/package.json new file mode 100644 index 0000000..1878088 --- /dev/null +++ b/uniapp/uni_modules/uni-tag/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-tag", + "displayName": "uni-tag 标签", + "version": "2.1.0", + "description": "Tag 组件,用于展示1个或多个文字标签,可点击切换选中、不选中的状态。", + "keywords": [ + "uni-ui", + "uniui", + "", + "tag", + "标签" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-tag/readme.md b/uniapp/uni_modules/uni-tag/readme.md new file mode 100644 index 0000000..6e78ff5 --- /dev/null +++ b/uniapp/uni_modules/uni-tag/readme.md @@ -0,0 +1,13 @@ + + +## Tag 标签 +> **组件名:uni-tag** +> 代码块: `uTag` + + +用于展示1个或多个文字标签,可点击切换选中、不选中的状态 。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-tag) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + diff --git a/uniapp/uni_modules/uni-test/changelog.md b/uniapp/uni_modules/uni-test/changelog.md new file mode 100644 index 0000000..2f3b102 --- /dev/null +++ b/uniapp/uni_modules/uni-test/changelog.md @@ -0,0 +1,39 @@ +## 2.0.13(2021-11-18) +邱 +## 2.0.12(2021-11-18) +123123 +## 2.0.11(2021-11-18) +0000 +## 2.0.10(2021-11-18) +111234 +## 2.0.9(2021-11-18) +3333 +## 2.0.8(2021-11-18) +=== +## 2.0.7(2021-11-18) +111 +## 2.0.6(2021-05-26) +- test +## 0.0.37(2021-03-23) +- uni-forms 更新 校验器 +- uni-forms 修复 表单规则设置类型为 number 的情况下,值为0校验失败的Bug + + +## 0.0.36(2021-03-23) +- uni-forms 更新 校验器 +- uni-forms 修复 表单规则设置类型为 number 的情况下,值为0校验失败的Bug + + +## 0.0.35(2021-03-23) +- uni-forms 更新 校验器 +- uni-forms 修复 表单规则设置类型为 number 的情况下,值为0校验失败的Bug + + +## 0.0.34(2021-03-23) +- 测试新同步插件 +## 0.0.33(2021-03-09) +- test +## 0.0.32(2021-02-24) +- 更新 read 2 +## 0.0.31(2021-02-24) +- 同步 readme.md diff --git a/uniapp/uni_modules/uni-test/components/uni-test/uni-test.vue b/uniapp/uni_modules/uni-test/components/uni-test/uni-test.vue new file mode 100644 index 0000000..9ea8aaf --- /dev/null +++ b/uniapp/uni_modules/uni-test/components/uni-test/uni-test.vue @@ -0,0 +1,26 @@ + + + + + diff --git a/uniapp/uni_modules/uni-test/package.json b/uniapp/uni_modules/uni-test/package.json new file mode 100644 index 0000000..9a3c4c4 --- /dev/null +++ b/uniapp/uni_modules/uni-test/package.json @@ -0,0 +1,83 @@ +{ + "id": "uni-test", + "displayName": "Test 测试插件", + "version": "2.0.13", + "description": "测试插件 ", + "keywords": [ + "test" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.1.3" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "y", + "联盟": "y" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} diff --git a/uniapp/uni_modules/uni-test/readme.md b/uniapp/uni_modules/uni-test/readme.md new file mode 100644 index 0000000..ddb24c6 --- /dev/null +++ b/uniapp/uni_modules/uni-test/readme.md @@ -0,0 +1,10 @@ + + +## DataCheckbox 数据驱动的单选复选框 +> **组件名:uni-data-checkbox** +> 代码块: `uDataCheckbox` + + +本组件是基于uni-app基础组件checkbox的封装。本组件要解决问题包括: + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-card) \ No newline at end of file diff --git a/uniapp/uni_modules/uni-title/changelog.md b/uniapp/uni_modules/uni-title/changelog.md new file mode 100644 index 0000000..7626216 --- /dev/null +++ b/uniapp/uni_modules/uni-title/changelog.md @@ -0,0 +1,10 @@ +## 1.1.1(2022-05-19) +- 修改组件描述 +## 1.1.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-title](https://uniapp.dcloud.io/component/uniui/uni-title) +## 1.0.2(2021-05-12) +- 新增 示例地址 +- 修复 示例项目缺少组件的Bug +## 1.0.1(2021-02-05) +- 调整为uni_modules目录规范 diff --git a/uniapp/uni_modules/uni-title/components/uni-title/uni-title.vue b/uniapp/uni_modules/uni-title/components/uni-title/uni-title.vue new file mode 100644 index 0000000..bf4f926 --- /dev/null +++ b/uniapp/uni_modules/uni-title/components/uni-title/uni-title.vue @@ -0,0 +1,171 @@ + + + + + diff --git a/uniapp/uni_modules/uni-title/package.json b/uniapp/uni_modules/uni-title/package.json new file mode 100644 index 0000000..2249f5a --- /dev/null +++ b/uniapp/uni_modules/uni-title/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-title", + "displayName": "uni-title 章节标题", + "version": "1.1.1", + "description": "章节标题,通常用于记录页面标题,使用当前组件,uni-app 如果开启统计,将会自动统计页面标题", + "keywords": [ + "uni-ui", + "uniui", + "标题", + "章节", + "章节标题", + "" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-title/readme.md b/uniapp/uni_modules/uni-title/readme.md new file mode 100644 index 0000000..0e60b1b --- /dev/null +++ b/uniapp/uni_modules/uni-title/readme.md @@ -0,0 +1,14 @@ + + +## Title 标题 +> **组件名:uni-title** +> 代码块: `uTitle` + + +章节标题,通常用于记录页面标题,使用当前组件,uni-app 如果开启统计,将会自动统计页面标题 。 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-title) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 + + + diff --git a/uniapp/uni_modules/uni-tooltip/changelog.md b/uniapp/uni_modules/uni-tooltip/changelog.md new file mode 100644 index 0000000..00f1572 --- /dev/null +++ b/uniapp/uni_modules/uni-tooltip/changelog.md @@ -0,0 +1,10 @@ +## 0.2.1(2022-05-09) +- 修复 content 为空时仍然弹出的bug +## 0.2.0(2022-05-07) +**注意:破坏性更新** +- 更新 text 属性变更为 content +- 更新 移除 width 属性 +## 0.1.1(2022-04-27) +- 修复 组件根 text 嵌套组件 warning +## 0.1.0(2022-04-21) +- 初始化 diff --git a/uniapp/uni_modules/uni-tooltip/components/uni-tooltip/uni-tooltip.vue b/uniapp/uni_modules/uni-tooltip/components/uni-tooltip/uni-tooltip.vue new file mode 100644 index 0000000..ffbb6fa --- /dev/null +++ b/uniapp/uni_modules/uni-tooltip/components/uni-tooltip/uni-tooltip.vue @@ -0,0 +1,68 @@ + + + + + + diff --git a/uniapp/uni_modules/uni-tooltip/package.json b/uniapp/uni_modules/uni-tooltip/package.json new file mode 100644 index 0000000..e88ecf8 --- /dev/null +++ b/uniapp/uni_modules/uni-tooltip/package.json @@ -0,0 +1,88 @@ +{ + "id": "uni-tooltip", + "displayName": "uni-tooltip 提示文字", + "version": "0.2.1", + "description": "Tooltip 提示文字", + "keywords": [ + "uni-tooltip", + "uni-ui", + "tooltip", + "tip", + "文字提示" + ], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无 ", + "data": "无", + "permissions": "无" + }, + "npmurl": "" + }, + "uni_modules": { + "dependencies": [], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "Vue": { + "vue2": "y", + "vue3": "y" + }, + "App": { + "app-vue": "y", + "app-nvue": "u" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "u", + "百度": "u", + "字节跳动": "u", + "QQ": "u", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-tooltip/readme.md b/uniapp/uni_modules/uni-tooltip/readme.md new file mode 100644 index 0000000..faafa2e --- /dev/null +++ b/uniapp/uni_modules/uni-tooltip/readme.md @@ -0,0 +1,8 @@ +## Badge 数字角标 +> **组件名:uni-tooltip** +> 代码块: `uTooltip` + +数字角标一般和其它控件(列表、9宫格等)配合使用,用于进行数量提示,默认为实心灰色背景, + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-tooltip) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 diff --git a/uniapp/uni_modules/uni-transition/changelog.md b/uniapp/uni_modules/uni-transition/changelog.md new file mode 100644 index 0000000..b1a824b --- /dev/null +++ b/uniapp/uni_modules/uni-transition/changelog.md @@ -0,0 +1,20 @@ +## 1.3.1(2021-11-23) +- 修复 init 方法初始化问题 +## 1.3.0(2021-11-19) +- 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-transition](https://uniapp.dcloud.io/component/uniui/uni-transition) +## 1.2.1(2021-09-27) +- 修复 init 方法不生效的 Bug +## 1.2.0(2021-07-30) +- 组件兼容 vue3,如何创建 vue3 项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +## 1.1.1(2021-05-12) +- 新增 示例地址 +- 修复 示例项目缺少组件的 Bug +## 1.1.0(2021-04-22) +- 新增 通过方法自定义动画 +- 新增 custom-class 非 NVUE 平台支持自定义 class 定制样式 +- 优化 动画触发逻辑,使动画更流畅 +- 优化 支持单独的动画类型 +- 优化 文档示例 +## 1.0.2(2021-02-05) +- 调整为 uni_modules 目录规范 diff --git a/uniapp/uni_modules/uni-transition/components/uni-transition/createAnimation.js b/uniapp/uni_modules/uni-transition/components/uni-transition/createAnimation.js new file mode 100644 index 0000000..8f89b18 --- /dev/null +++ b/uniapp/uni_modules/uni-transition/components/uni-transition/createAnimation.js @@ -0,0 +1,131 @@ +// const defaultOption = { +// duration: 300, +// timingFunction: 'linear', +// delay: 0, +// transformOrigin: '50% 50% 0' +// } +// #ifdef APP-NVUE +const nvueAnimation = uni.requireNativePlugin('animation') +// #endif +class MPAnimation { + constructor(options, _this) { + this.options = options + // 在iOS10+QQ小程序平台下,传给原生的对象一定是个普通对象而不是Proxy对象,否则会报parameter should be Object instead of ProxyObject的错误 + this.animation = uni.createAnimation({ + ...options + }) + this.currentStepAnimates = {} + this.next = 0 + this.$ = _this + + } + + _nvuePushAnimates(type, args) { + let aniObj = this.currentStepAnimates[this.next] + let styles = {} + if (!aniObj) { + styles = { + styles: {}, + config: {} + } + } else { + styles = aniObj + } + if (animateTypes1.includes(type)) { + if (!styles.styles.transform) { + styles.styles.transform = '' + } + let unit = '' + if(type === 'rotate'){ + unit = 'deg' + } + styles.styles.transform += `${type}(${args+unit}) ` + } else { + styles.styles[type] = `${args}` + } + this.currentStepAnimates[this.next] = styles + } + _animateRun(styles = {}, config = {}) { + let ref = this.$.$refs['ani'].ref + if (!ref) return + return new Promise((resolve, reject) => { + nvueAnimation.transition(ref, { + styles, + ...config + }, res => { + resolve() + }) + }) + } + + _nvueNextAnimate(animates, step = 0, fn) { + let obj = animates[step] + if (obj) { + let { + styles, + config + } = obj + this._animateRun(styles, config).then(() => { + step += 1 + this._nvueNextAnimate(animates, step, fn) + }) + } else { + this.currentStepAnimates = {} + typeof fn === 'function' && fn() + this.isEnd = true + } + } + + step(config = {}) { + // #ifndef APP-NVUE + this.animation.step(config) + // #endif + // #ifdef APP-NVUE + this.currentStepAnimates[this.next].config = Object.assign({}, this.options, config) + this.currentStepAnimates[this.next].styles.transformOrigin = this.currentStepAnimates[this.next].config.transformOrigin + this.next++ + // #endif + return this + } + + run(fn) { + // #ifndef APP-NVUE + this.$.animationData = this.animation.export() + this.$.timer = setTimeout(() => { + typeof fn === 'function' && fn() + }, this.$.durationTime) + // #endif + // #ifdef APP-NVUE + this.isEnd = false + let ref = this.$.$refs['ani'] && this.$.$refs['ani'].ref + if(!ref) return + this._nvueNextAnimate(this.currentStepAnimates, 0, fn) + this.next = 0 + // #endif + } +} + + +const animateTypes1 = ['matrix', 'matrix3d', 'rotate', 'rotate3d', 'rotateX', 'rotateY', 'rotateZ', 'scale', 'scale3d', + 'scaleX', 'scaleY', 'scaleZ', 'skew', 'skewX', 'skewY', 'translate', 'translate3d', 'translateX', 'translateY', + 'translateZ' +] +const animateTypes2 = ['opacity', 'backgroundColor'] +const animateTypes3 = ['width', 'height', 'left', 'right', 'top', 'bottom'] +animateTypes1.concat(animateTypes2, animateTypes3).forEach(type => { + MPAnimation.prototype[type] = function(...args) { + // #ifndef APP-NVUE + this.animation[type](...args) + // #endif + // #ifdef APP-NVUE + this._nvuePushAnimates(type, args) + // #endif + return this + } +}) + +export function createAnimation(option, _this) { + if(!_this) return + clearTimeout(_this.timer) + return new MPAnimation(option, _this) +} diff --git a/uniapp/uni_modules/uni-transition/components/uni-transition/uni-transition.vue b/uniapp/uni_modules/uni-transition/components/uni-transition/uni-transition.vue new file mode 100644 index 0000000..a2bf104 --- /dev/null +++ b/uniapp/uni_modules/uni-transition/components/uni-transition/uni-transition.vue @@ -0,0 +1,281 @@ + + + + + diff --git a/uniapp/uni_modules/uni-transition/package.json b/uniapp/uni_modules/uni-transition/package.json new file mode 100644 index 0000000..d15fdf0 --- /dev/null +++ b/uniapp/uni_modules/uni-transition/package.json @@ -0,0 +1,87 @@ +{ + "id": "uni-transition", + "displayName": "uni-transition 过渡动画", + "version": "1.3.1", + "description": "元素的简单过渡动画", + "keywords": [ + "uni-ui", + "uniui", + "动画", + "过渡", + "过渡动画" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "category": [ + "前端组件", + "通用组件" + ], + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui" + }, + "uni_modules": { + "dependencies": ["uni-scss"], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-transition/readme.md b/uniapp/uni_modules/uni-transition/readme.md new file mode 100644 index 0000000..2f8a77e --- /dev/null +++ b/uniapp/uni_modules/uni-transition/readme.md @@ -0,0 +1,11 @@ + + +## Transition 过渡动画 +> **组件名:uni-transition** +> 代码块: `uTransition` + + +元素过渡动画 + +### [查看文档](https://uniapp.dcloud.io/component/uniui/uni-transition) +#### 如使用过程中有任何问题,或者您对uni-ui有一些好的建议,欢迎加入 uni-ui 交流群:871950839 \ No newline at end of file diff --git a/uniapp/uni_modules/uni-ui/changelog.md b/uniapp/uni_modules/uni-ui/changelog.md new file mode 100644 index 0000000..61b26d0 --- /dev/null +++ b/uniapp/uni_modules/uni-ui/changelog.md @@ -0,0 +1,541 @@ +## 1.4.27(2023-04-23) +- uni-calendar 修复 某些情况 monthSwitch 未触发的Bug +- uni-calendar 修复 某些情况切换月份错误的Bug +- uni-data-picker 修复 更改 modelValue 报错的 bug +- uni-data-picker 修复 v-for 未使用 key 值控制台 warning +- uni-data-picker 修复代码合并时引发 value 属性为空时不渲染数据的问题 +- uni-data-picker 修复 localdata 不支持动态更新的bug +- uni-data-select 修复 微信小程序点击时会改变背景颜色的 bug +- uni-data-select 修复 禁用时会显示清空按钮 +- uni-data-select 优化 查询条件短期内多次变更只查询最后一次变更后的结果 +- uni-data-select 调整 内部缓存键名调整为 uni-data-select-lastSelectedValue +- uni-datetime-picker 修复 日历 picker 修改年月后,自动选中当月1日 [详情](https://ask.dcloud.net.cn/question/165937) +- uni-datetime-picker 修复 小程序端 低版本 ios NaN [详情](https://ask.dcloud.net.cn/question/162979) +- uni-datetime-picker 修复 firefox 浏览器显示区域点击无法拉起日历弹框的Bug [详情](https://ask.dcloud.net.cn/question/163362) +- uni-datetime-picker 优化 值为空依然选中当天问题 +- uni-datetime-picker 优化 提供 default-value 属性支持配置选择器打开时默认显示的时间 +- uni-datetime-picker 优化 非范围选择未选择日期时间,点击确认按钮选中当前日期时间 +- uni-datetime-picker 优化 字节小程序日期时间范围选择,底部日期换行问题 +- uni-datetime-picker 修复 2.2.18 引起范围选择配置 end 选择无效的Bug [详情](https://github.com/dcloudio/uni-ui/issues/686) +- uni-datetime-picker 修复 移动端范围选择change事件触发异常的Bug [详情](https://github.com/dcloudio/uni-ui/issues/684) +- uni-datetime-picker 优化 PC端输入日期格式错误时返回当前日期时间 +- uni-datetime-picker 优化 PC端输入日期时间超出 start、end 限制的Bug +- uni-datetime-picker 优化 移动端日期时间范围用法时间展示不完整问题 +- uni-datetime-picker 修复 小程序端绑定 Date 类型报错的Bug [详情](https://github.com/dcloudio/uni-ui/issues/679) +- uni-datetime-picker 修复 vue3 time-picker 无法显示绑定时分秒的Bug +- uni-datetime-picker 修复 字节小程序报错的Bug +- uni-datetime-picker 修复 某些情况切换月份错误的Bug +- uni-easyinput 修复 vue3 下 keyboardheightchange 事件报错的bug +- uni-easyinput 优化 trim 属性默认值 +- uni-easyinput 新增 cursor-spacing 属性 +- uni-fab 新增 pattern.icon 属性,可自定义图标 +- uni-file-picker 修复 手动上传删除一个文件后不能再上传的bug +- uni-forms 修复 required 参数无法动态绑定 +- uni-list 优化 uni-list-chat 具名插槽`header` 非app端套一层元素,方便使用时通过外层元素定位实现样式修改 +- uni-list uni-list-chat 新增 支持具名插槽`header` +- uni-list 新增 列表图标新增 customPrefix 属性 ,用法 [详见](https://uniapp.dcloud.net.cn/component/uniui/uni-icons.html#icons-props) +- uni-nav-bar 修复 自定义状态栏高度闪动BUG +- uni-nav-bar 修复 暗黑模式下边线颜色错误的bug +- uni-popup 修复 uni-popup 重复打开时的 bug +- uni-popup uni-popup-dialog 组件新增 inputType 属性 +- uni-swipe-action 修复`uni-swipe-action`和`uni-swipe-action-item`不同时使用导致 closeOther 方法报错的 bug +- uni-table 修复 在vue3模式下可能会出现错误的问题 +## 1.4.26(2023-01-31) +- uni-badge 修复 运行/打包 控制台警告问题 +- uni-calendar 修复 某些情况切换月份错误问题 +- uni-data-select 修复 不关联服务空间报错的问题 +- uni-data-select 新增 属性 `format` 可用于格式化显示选项内容 +- uni-datetime-picker 修复 某些情况切换月份错误问题 +- uni-easyinput 新增 keyboardheightchange 事件,可监听键盘高度变化 +- uni-list 修复 无反馈效果呈现的bug +## 1.4.25(2023-01-11) +- uni-file-picker 新增 sourceType 属性, 可以自定义图片和视频选择的来源 +## 1.4.24(2023-01-11) +- uni-data-select 修复 当where变化时,数据不会自动更新的问题 +- uni-datetime-picker 修复 多次加载组件造成内存占用的 bug +- uni-datetime-picker 修复 vue3 下 i18n 国际化初始值不正确的 bug +- uni-easyinput 修复 props 中背景颜色无默认值的bug +- uni-list 修复 uni-list-chat 在vue3下跳转报错的bug +- uni-list 修复 uni-list-chat avatar属性 值为本地路径时错误的问题 +- uni-list 修复 uni-list-chat avatar属性 在腾讯云版uniCloud下错误的问题 +- uni-list 修复 uni-list-chat note属性 支持:“草稿”字样功能 文本少1位的问题 +- uni-list 修复 uni-list-item 的 customStyle 属性 padding值在 H5端 无效的bug +- uni-list 修复 uni-list-item 的 customStyle 属性 padding值在nvue(vue2)下无效的bug +- uni-list uni-list-chat 新增 avatar 支持 fileId +- uni-list uni-list 新增属性 render-reverse 详情参考:[https://uniapp.dcloud.net.cn/component/list.html](https://uniapp.dcloud.net.cn/component/list.html) +- uni-list uni-list-chat note属性 支持:“草稿”字样 加红显示 详情参考uni-im:[https://ext.dcloud.net.cn/plugin?name=uni-im](https://ext.dcloud.net.cn/plugin?name=uni-im) +- uni-list uni-list-item 新增属性 customStyle 支持设置padding、backgroundColor +- uni-popup 修复 nvue 下 v-show 报错 +## 1.4.23(2022-10-25) +- uni-datetime-picker 修复,支付宝小程序样式错乱,[详情](https://github.com/dcloudio/uni-app/issues/3861) + +- uni-nav-bar 修复 条件编译错误的bug +- uni-nav-bar 修复 nvue 环境 fixed 为 true 的情况下,无法置顶的 bug +## 1.4.22(2022-09-19) +- 优化 部分组件适配 uni-scss 主题色 +- uni-badge 修复 当 text 超过 max-num 时,badge 的宽度计算是根据 text 的长度计算,更改为 css 计算实际展示宽度,详见:[https://ask.dcloud.net.cn/question/150473](https://ask.dcloud.net.cn/question/150473) +- uni-calendar 修复 表头年月切换,导致改变当前日期为选择月1号,且未触发change事件 +- uni-data-select 修复 微信小程序下拉框出现后选择会点击到蒙板后面的输入框 +- uni-data-select 修复 点击的位置不准确 +- uni-data-select 新增 支持 disabled 属性 +- uni-datetime-picker 修复,反向选择日期范围,日期显示异常,[详情](https://ask.dcloud.net.cn/question/153401?item_id=212892&rf=false) +- uni-datetime-picker 修复 close事件无效的 bug +- uni-datetime-picker 修复 移动端 maskClick 无效的 bug,详见:[https://ask.dcloud.net.cn/question/140824?item_id=209458&rf=false](https://ask.dcloud.net.cn/question/140824?item_id=209458&rf=false) +- uni-fab 修复 小程序端由于 style 使用了对象导致报错,[详情](https://ask.dcloud.net.cn/question/152790?item_id=211778&rf=false) +- uni-fab 修复 nvue 环境下,具有 tabBar 时,fab 组件下部位置无法正常获取 --window-bottom 的bug,详见:[https://ask.dcloud.net.cn/question/110638?notification_id=826310](https://ask.dcloud.net.cn/question/110638?notification_id=826310) +- uni-forms 优化 根据 rules 自动添加 required 的问题 +- uni-forms 修复 item 未设置 require 属性,rules 设置 require 后,星号也显示的 bug,详见:[https://ask.dcloud.net.cn/question/151540](https://ask.dcloud.net.cn/question/151540) +- uni-nav-bar 修复 nvue 环境下 fixed 为 true 的情况下,无法置顶的 bug +- uni-notice-bar 新增 属性 fontSize,可修改文字大小。 +- uni-pagination 修复,未对主题色设置默认色,导致未引入 uni-scss 变量文件报错。 +- uni-pagination 修复,未对移动端当前页文字做主题色适配。 +- uni-pagination 修复 es 语言 i18n 错误 +## 1.4.21(2022-09-19) +- 修复,安装时未导入 uni-data-select 和 uni-tooltip 的问题。 +## 1.4.20(2022-07-25) +- uni-section 新增组件 +- uni-forms 修复 model 需要校验的值没有声明对应字段时,导致第一次不触发校验的bug + +## 1.4.19(2022-07-07) +- uni-data-picker 优化 pc端图标位置不正确的问题 +- uni-data-select 修复 pc端宽度异常的bug +## 1.4.18(2022-07-06) +- uni-forms 【重要】组件逻辑重构,部分用法旧版本不兼容,请注意兼容问题 +- uni-forms 【重要】组件使用 Provide/Inject 方式注入依赖,提供了自定义表单组件调用 uni-forms 校验表单的能力 +- uni-forms 新增 更多表单示例 +- uni-forms 新增 model 属性,等同于原 value/modelValue 属性,旧属性即将废弃 +- uni-forms 新增 validateTrigger 属性的 blur 值,仅 uni-easyinput 生效 +- uni-forms 新增 onFieldChange 方法,可以对子表单进行校验,可替代binddata方法 +- uni-forms 新增 子表单的 setRules 方法,配合自定义校验函数使用 +- uni-forms 新增 uni-forms-item 的 setRules 方法,配置动态表单使用可动态更新校验规则 +- uni-forms 修复 由 1.4.0 引发的 label 插槽不生效的bug +- uni-forms 修复 子组件找不到 setValue 报错的bug +- uni-forms 修复 uni-data-picker 在 uni-forms-item 中报错的bug +- uni-forms 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug +- uni-forms 修复 表单校验顺序无序问题 +- uni-forms 优化 子表单组件uni-datetime-picker、uni-data-select、uni-data-picker的显示样式 +- uni-forms 优化 动态表单校验方式,废弃拼接name的方式 +- uni-breadcrumb 修复 微信小程序 separator 不显示问题 +- uni-data-checkbox 优化 在 uni-forms 中的依赖注入方式 +- uni-data-picker 修复 uni-data-picker 在 uni-forms-item 中宽度不正确的bug +- uni-data-picker 优化 显示样式 +- uni-data-select 优化 显示样式 +- uni-datetime-picker 修复 日历顶部年月及底部确认未国际化 bug +- uni-datetime-picker 优化 组件样式,调整了组件图标大小、高度、颜色等,与uni-ui风格保持一致 +- uni-easyinput 新增 在 uni-forms 1.4.0 中使用可以在 blur 时校验内容 +- uni-easyinput 新增 clear 事件,点击右侧叉号图标触发 +- uni-easyinput 新增 change 事件 ,仅在输入框失去焦点或用户按下回车时触发 +- uni-easyinput 优化 组件样式,组件获取焦点时高亮显示,图标颜色调整等 +- uni-easyinput 优化 clearable 显示策略 +- uni-file-picker 修复 在uni-forms下样式不生效的bug +- uni-nav-bar 修复 组件示例中插槽用法无法显示内容的bug +- uni-swipe-action 修复 vue3 下使用组件不能正常运行的Bug +- uni-swipe-action 修复 h5端点击click触发两次的Bug +- uni-table 修复 微信小程序存在无使用组件的问题 +## 1.4.17(2022-06-30) +- 支持 ios 安全区 +## 1.4.16(2022-06-06) +- uni-breadcrumb 新增 支持 uni.scss 修改颜色 +- uni-data-select 修复 localdata 赋值不生效的 bug +- uni-data-select 新增 支持选项禁用(数据选项设置 disabled: true 即禁用) +- uni-data-select 修复 当 value 为 0 时选择不生效的 bug +- uni-easyinput 修复 关闭图标某些情况下无法取消的bug +- uni-fav 新增 stat 属性 ,是否开启uni统计功能 +- uni-goods-nav 新增 stat属性,是否开启uni统计功能 +- uni-group 新增 stat属性,是否开启uni统计功能 +- uni-nav-bar 新增 stat 属性 ,可开启统计 title 上报 ,仅使用了title 属性且项目开启了uni统计生效 +- uni-search-bar 新增 readonly 属性,组件只读 +- uni-swipe-action 修复 isPC 找不到的Bug +- uni-swipe-action 修复 在 nvue 下 disabled 失效的bug +- uni-tooltip 修复 content 为空时仍然弹出的bug +## 1.4.15(2022-05-07) +- uni-data-picker 修复 字节小程序 本地数据无法选择下一级的Bug +- uni-data-select 新增 记住上次的选项(仅 collection 存在时有效) +- uni-search-bar 修复 vue3 input 事件不生效的bug +- uni-search-bar 修复 多余代码导致的bug +- uni-tooltip 更新 text 属性变更为 content +- uni-tooltip 更新 移除 width 属性 +- uni-tooltip 修复 组件根 text 嵌套组件 warning +## 1.4.14(2022-04-18) +- uni-datetime-picker 修复 Vue3 下动态赋值,单选类型未响应的 bug +- uni-easyinput 修复 默认值不生效的bug +## 1.4.13(2022-04-02) +- uni-calendar 修复 条件编译 nvue 不支持的 css 样式 +- uni-calendar 修复 startDate、 endDate 属性失效的 bug +- uni-data-picker 修复 nvue 不支持的 v-show 的 bug +- uni-data-picker 修复 条件编译 nvue 不支持的 css 样式 +- uni-datetime-picker 修复 Vue3 下动态赋值未响应的 bug +- uni-easyinput 修复 value不能为0的bug +- uni-popup 修复 弹出层内部无法滚动的bug +- uni-popup 修复 小程序中高度错误的bug +- uni-popup 修复 快速调用open出现问题的Bug +- uni-rate 修复 条件判断 `NaN` 错误的 bug +- uni-swipe-action 修复 按钮字体大小不能设置的bug +- uni-swipe-action 修复 h5和app端下报el错误的bug +- uni-swipe-action 修复 HBuilderX 1.4.X 版本中,h5和app端下报错的bug +## 1.4.12(2022-02-19) +- uni-collapse 修复 初始化的时候 ,open 属性失效的bug +- uni-data-checkbox 修复 multiple 为 true 时,v-model 的值为 null 报错的 bug +- uni-icons 优化 size 属性可以传入不带单位的字符串数值 +- uni-icons 优化 size 支持其他单位 +- uni-nav-bar 新增 left-width/right-width属性 ,可修改左右两侧的宽度 +- uni-popup 修复 safeArea 属性不能设置为false的bug +## 1.4.11(2022-01-21) +- uni-collapse 修复 微信小程序resize后组件收起的bug +- uni-countdown 修复 在微信小程序中样式不生效的bug +- uni-countdown 新增 update 方法 ,在动态更新时间后,刷新组件 +- uni-load-more 新增 showText属性 ,是否显示文本 +- uni-load-more 修复 nvue 平台下不显示文本的bug +- uni-load-more 修复 微信小程序平台样式选择器报警告的问题 +- uni-nav-bar 修复 在vue下,标题不垂直居中的bug +- uni-nav-bar 修复 height 属性类型错误 +- uni-nav-bar 新增 height 属性,可修改组件高度 +- uni-nav-bar 新增 dark 属性可可开启暗黑模式 +- uni-nav-bar 优化 标题字数过多显示省略号 +- uni-nav-bar 优化 插槽,插入内容可完全覆盖 +- uni-popup 修复 isMaskClick 失效的bug +- uni-popup 新增 cancelText \ confirmText 属性 ,可自定义文本 +- uni-popup 新增 maskBackgroundColor 属性 ,可以修改蒙版颜色 +- uni-popup 优化 maskClick属性 更新为 isMaskClick ,解决微信小程序警告的问题 + +## 1.4.10(2022-01-17) +- uni-card 修复 在vue页面下略缩图显示不正常的bug +- uni-datetime-picker 修复 clear-icon 属性在小程序平台不生效的 bug +- uni-datetime-picker 修复 日期范围选在小程序平台,必须多点击一次才能取消选中状态的 bug +- uni-fab 更新 组件依赖 +- +- uni-icons 修复 nvue 有些图标不显示的bug,兼容老版本图标 +- uni-icons 优化 示例可复制图标名称 +- uni-nav-bar 修复 color 属性不生效的bug +- uni-popup 修复 设置 safeArea 属性不生效的bug +- uni-popup 优化 组件示例 +- uni-popup 修复 vuedoc 文字错误 +## 1.4.9(2021-11-23) +- uni-ui 修复 vue3中某些scss变量无法找到的问题 +- uni-combox 优化 label、label-width 属性 +- uni-data-picker 修复 由上个版本引发的map、v-model等属性不生效的bug +- uni-file-picker 修复 参数为对象的情况下,url在某些情况显示错误的bug +- uni-icons 优化 兼容旧组件 type 值 +- uni-list 修复 在vue3中to属性在发行应用的时候报错的bug +- uni-scss 修复 vue3中scss语法兼容问题 +- uni-transition 修复 init 方法初始化问题 +## 1.4.8(2021-11-19) +- uni-fab 修复 阴影颜色不正确的bug +## 1.4.7(2021-11-19) +- uni-ui 新增 支持国际化 +- uni-ui 优化 组件UI,并提供设计资源,详见:[https://uniapp.dcloud.io/component/uniui/resource](https://uniapp.dcloud.io/component/uniui/resource) +- uni-ui 文档迁移,详见:[https://uniapp.dcloud.io/component/uniui/uni-ui](https://uniapp.dcloud.io/component/uniui/uni-ui) +- uni-badge 修改 size 属性默认值调整为 small +- uni-badge 修改 type 属性,默认值调整为 error,info 替换 default +- uni-badge 修复 在字节小程序上样式不生效的 bug +- uni-calendar 修复 弹出层被 tabbar 遮盖 bug +- uni-card 重构插槽的用法 ,header 替换为 title +- uni-card 新增 actions 插槽 +- uni-card 新增 cover 封面图属性和插槽 +- uni-card 新增 padding 内容默认内边距离 +- uni-card 新增 margin 卡片默认外边距离 +- uni-card 新增 spacing 卡片默认内边距 +- uni-card 新增 shadow 卡片阴影属性 +- uni-card 取消 mode 属性,可使用组合插槽代替 +- uni-card 取消 note 属性 ,使用actions插槽代替 +- uni-collapse 优化 show-arrow 属性默认为true +- uni-collapse 新增 show-arrow 属性,控制是否显示右侧箭头 +- uni-countdown 新增 font-size 支持自定义字体大小 +- uni-data-checkbox 修复 在uni-forms中 modelValue 中不存在当前字段,当前字段必填写也不参与校验的问题 +- uni-data-checkbox 修复 单选 list 模式下 ,icon 为 left 时,选中图标不显示的问题 +- uni-data-checkbox 修复 在 uni-forms 中重置表单,错误信息无法清除的问题 +- uni-dateformat 优化 默认时间不再是当前时间,而是显示'-'字符 +- uni-datetime-picker 修复 hide-second 在移动端的 bug +- uni-datetime-picker 修复 单选赋默认值时,赋值日期未高亮的 bug +- uni-datetime-picker 修复 赋默认值时,移动端未正确显示时间的 bug +- uni-datetime-picker 新增 hide-second 属性,支持只使用时分,隐藏秒 +- uni-datetime-picker 优化 取消选中时(范围选)直接开始下一次选择, 避免多点一次 +- uni-datetime-picker 优化 移动端支持清除按钮,同时支持通过 ref 调用组件的 clear 方法 +- uni-datetime-picker 优化 调整字号大小,美化日历界面 +- uni-datetime-picker 优化 范围选择器在 pc 端过宽的问题 +- uni-datetime-picker 新增 支持作为 uni-forms 子组件相关功能 +- uni-datetime-picker 修复 在 uni-forms 中使用时,选择时间报 NAN 错误的 bug +- uni-datetime-picker 修复 type 属性动态赋值无效的 bug +- uni-datetime-picker 修复 ‘确认’按钮被 tabbar 遮盖 bug +- uni-datetime-picker 修复 组件未赋值时范围选左、右日历相同的 bug +- uni-datetime-picker 修复 范围选未正确显示当前值的 bug +- uni-datetime-picker 修复 h5 平台(移动端)报错 'cale' of undefined 的 bug +- uni-easyinput 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug +- uni-easyinput 修复 在 uni-forms 中重置表单,错误信息无法清除的问题 +- uni-file-picker 新增 参数中返回 fileID 字段 +- uni-file-picker 修复 腾讯云传入fileID 不能回显的bug +- uni-file-picker 修复 选择图片后,不能放大的问题 +- uni-file-picker 修复 由于 0.2.11 版本引起的不能回显图片的Bug +- uni-file-picker 新增 clearFiles(index) 方法,可以手动删除指定文件 +- uni-file-picker 修复 v-model 值设为 null 报错的Bug +- uni-file-picker 修复 return-type="object" 时,无法删除文件的Bug +- uni-file-picker 修复 auto-upload 属性失效的Bug +- uni-forms 修复 label 插槽不生效的bug +- uni-forms 修复 没有添加校验规则的字段依然报错的Bug +- uni-forms 修复 重置表单错误信息无法清除的问题 +- uni-forms 修复 表单验证只生效一次的问题 +- uni-icons 新增 更多图标 +- uni-icons 优化 自定义图标使用方式 +- uni-link 修复 在 nvue 下不显示的 bug +- uni-pagination 修复 current 、value 属性未监听,导致高亮样式失效的 bug +- uni-rate 优化 默认值修改为 0 颗星 +- uni-search-bar 修复 value 属性与 modelValue 属性不兼容的Bug +- uni-swipe-action 新增 close-all 方法,关闭所有已打开的组件 +- uni-swipe-action 新增 resize() 方法,在非微信小程序、h5、app-vue端出现不能滑动的问题的时候,重置组件 +- uni-swipe-action 修复 app 端偶尔出现类似 Page[x][-x,xx;-x,xx,x,x-x] 的问题 +- uni-swipe-action 优化 微信小程序、h5、app-vue 滑动逻辑,避免出现动态新增组件后不能滑动的问题 +- uni-tag 新增 提供组件设计资源,组件样式调整 +- uni-tag 移除 插槽 +- uni-tag 移除 type 属性的 royal 选项 +- uni-tag type 不是 default 时,size 为 small 字体大小显示不正确 +## 1.4.2(2021-08-20) +- 新增 uni-ui 组件支持国际化 i18n +- uni-collapse 优化 show-arrow 属性默认为true +- uni-collapse 新增 show-arrow 属性,控制是否显示右侧箭头 +- uni-data-checkbox 修复 单选 list 模式下 ,icon 为 left 时,选中图标不显示的问题 +- uni-easyinput 修复 在 uni-forms 的动态表单中默认值校验不通过的 bug +- uni-file-picker 修复 由于 0.2.11 版本引起的不能回显图片的Bug +- uni-file-picker 新增 clearFiles(index) 方法,可以手动删除指定文件 +- uni-file-picker 修复 v-model 值设为 null 报错的Bug +- uni-swipe-action 新增 close-all 方法,关闭所有已打开的组件 +- uni-swipe-action 新增 resize() 方法,在非微信小程序、h5、app-vue端出现不能滑动的问题的时候,重置组件 +- uni-swipe-action 修复 app 端偶尔出现类似 Page[x][-x,xx;-x,xx,x,x-x] 的问题 +- uni-swipe-action 优化 微信小程序、h5、app-vue 滑动逻辑,避免出现动态新增组件后不能滑动的问题 +## 1.4.0(2021-08-13) +- uni-calendar 修复 弹出层被 tabbar 遮盖 bug +- uni-data-checkbox 修复 在 uni-forms 中重置表单,错误信息无法清除的问题 +- uni-dateformat 调整 默认时间不再是当前时间,而是显示'-'字符 +- uni-datetime-picker 新增 适配 vue3 +- uni-datetime-picker 新增 支持作为 uni-forms 子组件相关功能 +- uni-datetime-picker 修复 在 uni-forms 中使用时,选择时间报 NAN 错误的 bug +- uni-datetime-picker 修复 type 属性动态赋值无效的 bug +- uni-datetime-picker 修复 ‘确认’按钮被 tabbar 遮盖 bug +- uni-datetime-picker 修复 组件未赋值时范围选左、右日历相同的 bug +- uni-datetime-picker 修复 范围选未正确显示当前值的 bug +- uni-datetime-picker 修复 h5 平台(移动端)报错 'cale' of undefined 的 bug +- uni-easyinput 修复 在 uni-forms 中重置表单,错误信息无法清除的问题 +- uni-file-picker 修复 return-type="object" 时,无法删除文件的Bug +- uni-file-picker 修复 auto-upload 属性失效的Bug +- uni-forms 修复 没有添加校验规则的字段依然报错的Bug +- uni-forms 修复 重置表单错误信息无法清除的问题 +- uni-forms 优化 组件文档 +- uni-forms 修复 表单验证只生效一次的问题 +- uni-tag type 不是 default 时,size 为 small 字体大小显示不正确 +## 1.3.9(2021-08-02) +- uni-datetime-picker 新增 return-type 属性支持返回 date 日期对象 +- uni-file-picker 修复 fileExtname属性不指定值报错的Bug +- uni-file-picker 修复 在某种场景下图片不回显的Bug +- uni-link 支持自定义插槽 +## 1.3.8(2021-07-31) +- uni-ui 组件兼容 vue3 +- uni-collapse 修复 由1.2.0版本引起的 change 事件返回 undefined 的Bug +- uni-collapse 优化 组件示例 +- uni-collapse 新增 组件折叠动画 +- uni-collapse 新增 value\v-model 属性 ,动态修改面板折叠状态 +- uni-collapse 新增 title 插槽 ,可定义面板标题 +- uni-collapse 新增 border 属性 ,显示隐藏面板内容分隔线 +- uni-collapse 新增 title-border 属性 ,显示隐藏面板标题分隔线 +- uni-collapse 修复 resize 方法失效的Bug +- uni-collapse 修复 change 事件返回参数不正确的Bug +- uni-collapse 优化 H5、App 平台自动更具内容更新高度,无需调用 reszie() 方法 +- uni-data-checkbox 优化 在uni-forms组件,与label不对齐的问题 +- uni-data-checkbox 修复 单选默认值为0不能选中的Bug +- uni-easyinput 优化 errorMessage 属性支持 Boolean 类型 +- uni-file-picker 修复 return-type为object下,返回值不正确的Bug +- uni-file-picker 修复(重要) H5 平台下如果和uni-forms组件一同使用导致页面卡死的问题 +- uni-file-picker 优化 h5平台下上传文件导致页面卡死的问题 +- uni-forms 修复 vue2 下条件编译导致destroyed生命周期失效的Bug +- uni-forms 修复 1.2.1 引起的示例在小程序平台报错的Bug +- uni-forms 修复 动态校验表单,默认值为空的情况下校验失效的Bug +- uni-forms 修复 不指定name属性时,运行报错的Bug +- uni-forms 优化 label默认宽度从65调整至70,使required为true且四字时不换行 +- uni-forms 优化 组件示例,新增动态校验示例代码 +- uni-forms 优化 组件文档,使用方式更清晰 +- uni-list 修复 与其他组件嵌套使用时,点击失效的Bug +- uni-swipe-action 修复 跨页面修改组件数据 ,导致不能滑动的问题 +## 1.3.7(2021-07-16) +- uni-ui 兼容 vue3,如何创建vue3项目,详见 [uni-app 项目支持 vue3 介绍](https://ask.dcloud.net.cn/article/37834) +- uni-datetime-picker 修复 单选日期类型,初始赋值后不在当前日历的 bug +- uni-datetime-picker 新增 clearIcon 属性,显示框的清空按钮可配置显示隐藏(仅 pc 有效) +- uni-datetime-picker 优化 移动端移除显示框的清空按钮,无实际用途 +- uni-datetime-picker 修复 组件赋值为空,界面未更新的 bug +- uni-datetime-picker 修复 start 和 end 不能动态赋值的 bug +- uni-datetime-picker 修复 范围选类型,用户选择后再次选择右侧日历(结束日期)显示不正确的 bug +## 1.3.6(2021-07-09) +- uni-data-checkbox 优化 删除无用日志 +- uni-data-checkbox 修复 由 0.1.9 引起的非 nvue 端图标不显示的问题 +- uni-data-checkbox 修复 nvue 黑框样式问题 +- uni-datetime-picker 修复 范围选择不能动态赋值的 bug +- uni-datetime-picker 修复 范围选择的初始时间在一个月内时,造成无法选择的bug +- uni-datetime-picker 优化 弹出层在超出视窗边缘定位不准确的问题 +- uni-datetime-picker 修复 范围起始点样式的背景色与今日样式的字体前景色融合,导致日期字体看不清的 bug +- uni-datetime-picker 优化 弹出层在超出视窗边缘被遮盖的问题 +- uni-datetime-picker 新增 maskClick 事件 +- uni-datetime-picker 修复 特殊情况日历 rpx 布局错误的 bug,rpx -> px +- uni-datetime-picker 修复 范围选择时清空返回值不合理的bug,['', ''] -> [] +- uni-datetime-picker 新增 日期时间显示框支持插槽 +- uni-file-picker 修复 sourceType 缺少默认值导致 ios 无法选择文件 +- uni-file-picker 优化 解耦与uniCloud的强绑定关系 ,如不绑定服务空间,默认autoUpload为false且不可更改 +- uni-table 新增 uni-th 支持 date 日期筛选范围 +- uni-table 新增 uni-th 支持 range 筛选范围 +- uni-table 新增 uni-th 筛选功能 +## 1.3.5(2021-07-02) +- uni-card 优化 图文卡片无图片加载时,提供占位图标 +- uni-card 新增 header 插槽,自定义卡片头部( 图文卡片 mode="style" 时,不支持) +- uni-card 修复 thumbnail 不存在仍然占位的 bug +- uni-data-checkbox 修复 selectedTextColor 属性不生效的Bug +- uni-datetime-picker 优化 添加 uni-icons 依赖 +- uni-easyinput 修复 confirmType 属性(仅 type="text" 生效)导致多行文本框无法换行的 bug +- uni-file-picker 修复 由 0.0.10 版本引发的 returnType 属性失效的问题 +- uni-file-picker 优化 文件上传后进度条消失时机 +- uni-file-picker 修复 在uni-forms 中,删除文件 ,获取的值不对的Bug +- uni-forms 修复 pattern 属性在微信小程序平台无效的问题 +## 1.3.4(2021-06-25) +- uni-badge 优化 示例项目 +- uni-countdown 修复 uni-countdown 重复赋值跳两秒的 bug +- uni-easyinput 修复 passwordIcon 属性拼写错误的 bug +- uni-forms 修复 validate-trigger属性为submit且err-show-type属性为toast时不能弹出的Bug +- uni-forms 修复 只写setRules方法而导致校验不生效的Bug +- uni-forms 修复 由上个办法引发的错误提示文字错位的Bug +- uni-forms 修复 不设置 label 属性 ,无法设置label插槽的问题 +- uni-forms 修复 不设置label属性,label-width属性不生效的bug +- uni-forms 修复 setRules 方法与rules属性冲突的问题 +- uni-link 新增 download 属性,H5平台下载文件名 +- uni-popup 新增 mask-click 遮罩层点击事件 +- uni-popup 修复 nvue 平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug +- uni-tag 修复 uni-tag 在字节跳动小程序上 css 类名编译错误的 bug +## 1.3.3(2021-06-18) +- uni-easyinput 新增 passwordIcon 属性,当type=password时是否显示小眼睛图标 +- uni-easyinput 修复 confirmType 属性不生效的问题 +- uni-easyinput 修复 disabled 状态可清出内容的 bug +- uni-file-picker 修复 删除文件时无法触发 v-model 的Bug +- uni-popup 修复 H5平台中间弹出后,点击内容,再点击遮罩无法关闭的Bug +- uni-popup 修复 错误的 watch 字段 +- uni-popup 修复 safeArea 属性不生效的问题 +- uni-popup 修复 点击内容,再点击遮罩无法关闭的Bug +## 1.3.2(2021-06-04) +- uni-data-checkbox 新增 map 属性,可以方便映射text/value属性 +- uni-data-checkbox 修复 不关联服务空间的情况下组件报错的Bug +- uni-data-picker 修复 上个版本引出的本地数据无法选择带有children的2级节点 +- uni-forms 修复 动态删减数据导致报错的问题 +- uni-forms 新增 modelValue 属性 ,value 即将废弃 +- uni-forms 新增 uni-forms-item 可以设置单独的 rules +- uni-forms 新增 validate 事件增加 keepitem 参数,可以选择那些字段不过滤 +- uni-forms 优化 submit 事件重命名为 validate +- uni-data-picker 修复 无法加载云端数据的问题 +- uni-data-picker 修复 v-model无效问题 +- uni-data-picker 修复 loaddata 为空数据组时加载时间过长问题 +- uni-datetime-picker 修复 图标在小程序上不显示的 bug +- uni-datetime-picker 优化 重命名引用组件,避免潜在组件命名冲突 +- uni-datetime-picker 优化 代码目录扁平化 +- uni-tag 修复 未定义 sass 变量 "$uni-color-royal" 的bug +## 1.3.1(2021-05-14) +- uni-badge 新增 uni-badge 的 absolute 属性,支持定位 +- uni-badge 新增 uni-badge 的 offset 属性,支持定位偏移 +- uni-badge 新增 uni-badge 的 is-dot 属性,支持仅显示有一个小点 +- uni-badge 新增 uni-badge 的 max-num 属性,支持自定义封顶的数字值,超过 99 显示99+ +- uni-badge 优化 uni-badge 属性 custom-style, 支持以对象形式自定义样式 +- uni-badge 修复 uni-badge 在 App 端,数字小于10时不是圆形的bug +- uni-badge 修复 uni-badge 在父元素不是 flex 布局时,宽度缩小的bug +- uni-badge 新增 uni-badge 属性 custom-style, 支持自定义样式 +- uni-datetime-picker 修复 ios 下不识别 '-' 日期格式的 bug +- uni-datetime-picker 优化 pc 下弹出层添加边框和阴影 +- uni-datetime-picker 修复 在 admin 中获取弹出层定位错误的bug +- uni-datetime-picker 修复 type 属性向下兼容,默认值从 date 变更为 datetime +- uni-datetime-picker 支持日历形式的日期+时间的范围选择 +- uni-steps 修复 uni-steps 横向布局时,多行文字高度不合理的 bug +- uni-countdown 修复 uni-countdown 不能控制倒计时的 bug +- uni-tag 修复 royal 类型无效的bug +- uni-tag 修复 uni-tag 宽度不自适应的bug +- uni-tag 新增 uni-tag 支持属性 custom-style 自定义样式 +- uni-link 新增 href 属性支持 tel:|mailto: +- uni-popup 修复 组件内放置 input 、textarea 组件,无法聚焦的问题 +- uni-popup 新增 type 属性的 left\right 值,支持左右弹出 +- uni-popup 新增 open(String:type) 方法参数 ,可以省略 type 属性 ,直接传入类型打开指定弹窗 +- uni-popup 新增 backgroundColor 属性,可定义主窗口背景色,默认不显示背景色 +- uni-popup 新增 safeArea 属性,是否适配底部安全区 +- uni-popup 修复 App\h5\微信小程序底部安全区占位不对的Bug +- uni-popup 修复 App 端弹出等待的Bug +- uni-popup 优化 提升低配设备性能,优化动画卡顿问题 +- uni-popup 优化 更简单的组件自定义方式 +- uni-table 修复 示例项目缺少组件的Bug +- uni-forms 修复 自定义检验器失效的问题 +- uni-title 修复 示例项目缺少组件的Bug +- uni-transition 修复 示例项目缺少组件的Bug +- uni-swiper-dot 修复 示例项目缺少组件的Bug +- uni-ui 新增 组件示例地址 +## 1.3.0(2021-04-23) +- uni-combox 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-data-picker 修复 非树形数据有 where 属性查询报错的问题 +- uni-fav 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-goods-nav 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-nav-bar 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-notice-bar 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-number-box 修复 uni-number-box 浮点数运算不精确的 bug +- uni-number-box 修复 uni-number-box change 事件触发不正确的 bug +- uni-number-box 新增 uni-number-box v-model 双向绑定 +- uni-rate 修复 布局变化后 uni-rate 星星计算不准确的 bug +- uni-rate 优化 添加依赖 uni-icons, 导入 uni-rate 自动下载依赖 +- uni-search-bar 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-steps 优化 添加依赖 uni-icons, 导入后自动下载依赖 +- uni-transition 新增 通过方法自定义动画 +- uni-transition 新增 custom-class 非 NVUE 平台支持自定义 class 定制样式 +- uni-transition 优化 动画触发逻辑,使动画更流畅 +- uni-transition 优化 支持单独的动画类型 +- uni-transition 优化 文档示例 +## 1.2.13(2021-04-16) +- uni-ui 新增 uni-data-picker 支持云端非树形表结构数据 +- uni-ui 修复 uni-data-checkbox nvue 下无法选中的问题 +- uni-ui 修复 uni-data-picker 根节点 parent_field 字段等于null时选择界面错乱问题 +- uni-ui 修复 uni-file-picker 选择的文件非 file-extname 字段指定的扩展名报错的Bug +- uni-ui 修复 uni-swipe-action 报错 nv_navigator is not defined 的bug +- uni-ui 修复 uni-load-more 在首页使用时,h5 平台报 'uni is not defined' 的 bug +- uni-ui 优化 uni-file-picker file-extname 字段支持字符串写法,多个扩展名需要用逗号分隔 +- uni-ui 优化 uni-pagination PC 和 移动端适配不同的 ui +- uni-ui 更新 uni-file-picker 组件示例 +- uni-ui 修复 uni-nav-bar 当 fixed 属性为 true 时铺不满屏幕的 bug +- uni-ui 新增 uni-search-bar 的 focus 事件 +- uni-ui 修复 uni-rate 属性 margin 值为 string 组件失效的 bug +- uni-data-picker 修复 本地数据概率无法回显时问题 +- uni-table 新增 sortable 属性,是否开启单列排序 +- uni-table 优化 表格多选逻辑 +## 1.2.12(2021-03-23) +- uni-ui 新增 uni-datetime-picker 的 hide-second 属性、border 属性; +- uni-ui 修复 uni-datetime-picker 选择跟显示的日期不一样的 bug, +- uni-ui 修复 uni-datetime-picker change事件触发2次的 bug +- uni-ui 修复 uni-datetime-picker 分、秒 end 范围错误的 bug +- uni-ui 新增 uni-tr selectable 属性,用于 type=selection 时,设置某行是否可由全选按钮控制 +- uni-ui 新增 uni-data-checkbox 新增 disabled属性,支持nvue +- uni-ui 优化 uni-data-checkbox 无选项时提示“暂无数据” +- uni-ui 优化 uni-data-checkbox 默认颜色显示 +- uni-ui 新增 uni-link href 属性支持 tel:|mailto: +- uni-ui 新增 uni-table 示例demo +- uni-ui 修复 uni-data-picker 微信小程序某些情况下无法选择的问题,事件无法触发的问题 +- uni-ui 修复 uni-nav-bar easycom 下,找不到 uni-status-bar 的bug +- uni-ui 修复 uni-easyinput 示例在 qq 小程序上的bug +- uni-ui 修复 uni-forms 动态显示uni-forms-item的情况下,submit 方法获取值错误的Bug +- uni-ui 调整 cli 项目 建议使用 easycom 方式引用组件,如使用按需引用,需手动维护组件内部引用 + +## 1.2.11(2021-02-24) +- 调整为uni_modules目录规范 +- uni-data-picker 新增 数据驱动的picker选择器 +- uni-file-picker 新增 文件选择上传 +- uni-row 新增 栅格系统 +- uni-data-checkbox 优化 支持 nvue +- uni-forms 修复 偶发性获取表单值错误的Bug +- uni-forms 修复 校验 uni-data-picker value 为 0 时,返回值错误的Bug +- uni-forms 修复 uni-forms-item 组件隐藏时依然触发校验的bug +- uni-forms 优化 实时校验 +- uni-forms 优化 兼容nvue页面 +- uni-easyinput 优化 兼容nvue页面 +- uni-group 优化 兼容nvue页面 +- uni-popup 优化 组件适配 PC +- uni-fab 优化 适配 PC +- uni-swiper-dot 优化 适配 PC +- uni-rate 优化 适配 PC +- uni-notice-bar 优化 适配 PC +- uni-indexed-list 优化 适配 PC +- uni-combox 优化 适配 PC +- uni-transition 优化 适配 PC +- uni-nav-bar 优化 适配 PC +- uni-swipe-action 优化 适配 PC diff --git a/uniapp/uni_modules/uni-ui/components/uni-ui/uni-ui.vue b/uniapp/uni_modules/uni-ui/components/uni-ui/uni-ui.vue new file mode 100644 index 0000000..0970892 --- /dev/null +++ b/uniapp/uni_modules/uni-ui/components/uni-ui/uni-ui.vue @@ -0,0 +1,7 @@ + + + diff --git a/uniapp/uni_modules/uni-ui/package.json b/uniapp/uni_modules/uni-ui/package.json new file mode 100644 index 0000000..857ed51 --- /dev/null +++ b/uniapp/uni_modules/uni-ui/package.json @@ -0,0 +1,129 @@ +{ + "id": "uni-ui", + "displayName": "uni-ui", + "version": "1.4.27", + "description": "uni-ui 是基于uni-app的、全端兼容的、高性能UI框架", + "keywords": [ + "uni-ui", + "uniui", + "UI组件库", + "ui框架", + "ui库" +], + "repository": "https://github.com/dcloudio/uni-ui", + "engines": { + "HBuilderX": "^3.2.10" + }, + "directories": { + "example": "../../temps/example_temps" + }, + "dcloudext": { + "sale": { + "regular": { + "price": "0.00" + }, + "sourcecode": { + "price": "0.00" + } + }, + "contact": { + "qq": "" + }, + "declaration": { + "ads": "无", + "data": "无", + "permissions": "无" + }, + "npmurl": "https://www.npmjs.com/package/@dcloudio/uni-ui", + "type": "component-vue" + }, + "uni_modules": { + "dependencies": [ + "uni-badge", + "uni-calendar", + "uni-card", + "uni-collapse", + "uni-combox", + "uni-countdown", + "uni-data-checkbox", + "uni-data-picker", + "uni-data-select", + "uni-dateformat", + "uni-datetime-picker", + "uni-drawer", + "uni-easyinput", + "uni-fab", + "uni-fav", + "uni-file-picker", + "uni-forms", + "uni-goods-nav", + "uni-grid", + "uni-group", + "uni-icons", + "uni-indexed-list", + "uni-link", + "uni-list", + "uni-load-more", + "uni-nav-bar", + "uni-notice-bar", + "uni-number-box", + "uni-pagination", + "uni-popup", + "uni-rate", + "uni-row", + "uni-search-bar", + "uni-section", + "uni-segmented-control", + "uni-steps", + "uni-swipe-action", + "uni-swiper-dot", + "uni-table", + "uni-tag", + "uni-title", + "uni-tooltip", + "uni-transition" + ], + "encrypt": [], + "platforms": { + "cloud": { + "tcb": "y", + "aliyun": "y" + }, + "client": { + "App": { + "app-vue": "y", + "app-nvue": "y" + }, + "H5-mobile": { + "Safari": "y", + "Android Browser": "y", + "微信浏览器(Android)": "y", + "QQ浏览器(Android)": "y" + }, + "H5-pc": { + "Chrome": "y", + "IE": "y", + "Edge": "y", + "Firefox": "y", + "Safari": "y" + }, + "小程序": { + "微信": "y", + "阿里": "y", + "百度": "y", + "字节跳动": "y", + "QQ": "y", + "京东": "u" + }, + "快应用": { + "华为": "u", + "联盟": "u" + }, + "Vue": { + "vue2": "y", + "vue3": "y" + } + } + } + } +} \ No newline at end of file diff --git a/uniapp/uni_modules/uni-ui/readme.md b/uniapp/uni_modules/uni-ui/readme.md new file mode 100644 index 0000000..23f8456 --- /dev/null +++ b/uniapp/uni_modules/uni-ui/readme.md @@ -0,0 +1,247 @@ +> 当前插件不包含示例页面 ,如需示例请在 HBuiderX 中新建 `hello uni-app > 扩展组件` 中查看 +> +> 代码示例地址 :[https://ext.dcloud.net.cn/plugin?id=4941](https://ext.dcloud.net.cn/plugin?id=4941) +> +> 组件演示地址:[https://hellouniapp.dcloud.net.cn](https://hellouniapp.dcloud.net.cn/pages/extUI/badge/badge) +> +> 组件文档地址:[https://uniapp.dcloud.io/component/uniui/uni-ui](https://uniapp.dcloud.io/component/uniui/uni-ui) + +# uni-ui 介绍 + +## uni-ui产品特点 + +### 1. 高性能 + +目前为止,在小程序和混合app领域,暂时还没有比 `uni-ui` 更高性能的框架。 +- 自动差量更新数据 + +虽然uni-app支持小程序自定义组件,所有小程序的ui库都可以用。但小程序自定义组件的ui库都需要使用setData手动更新数据,在大数据量时、或高频更新数据时,很容易产生性能问题。 + +而 `uni-ui` 属于vue组件,uni-app引擎底层自动diff更新数据。当然其实插件市场里众多vue组件都具备这个特点。 +- 优化逻辑层和视图层通讯折损 + +非H5,不管是小程序还是App,不管是app的webview渲染还是原生渲染,全都是逻辑层和视图层分离的。这里就有一个逻辑层和视图层通讯的折损问题。 +比如在视图层拖动一个可跟手的组件,由于通讯的损耗,用js监听很难做到实时跟手。 + +这时就需要使用css动画以及平台底层提供的wxs、bindingx等技术。不过这些技术都比较复杂,所以 `uni-ui` 里做了封装,在需要跟手式操作的ui组件,比如swiperaction列表项左滑菜单,就在底层使用了这些技术,实现了高性能的交互体验 +- 背景停止 + +很多ui组件是会一直动的,比如轮播图、跑马灯。即便这个窗体被新窗体挡住,它在背景层仍然在消耗着硬件资源。在Android的webview版本为chrome66以上,背景操作ui会引发很严重的性能问题,造成前台界面明显卡顿。 + +而 `uni-ui` 的组件,会自动判断自己的显示状态,在组件不再可见时,不会再消耗硬件资源。 + +### 2. 全端 + + `uni-ui` 的组件都是多端自适应的,底层会抹平很多小程序平台的差异或bug。 + +比如导航栏navbar组件,会自动处理不同端的状态栏。 +比如swiperaction组件,在app和微信小程序上会使用交互体验更好的wxs技术,但在不支持wxs的其他小程序端会使用js模拟类似效果。 + + `uni-ui` 还支持nvue原生渲染,[详见](https://github.com/dcloudio/uni-ui/tree/nvue-uni-ui) + +未来 `uni-ui` 还会支持pc等大屏设备。 + +### 3. 与uni统计自动集成实现免打点 + +uni统计是优秀的多端统计平台,见[tongji.dcloud.net.cn](https://tongji.dcloud.net.cn)。 + +除了一张报表看全端,它的另一个重要特点是免打点。 +比如使用 `uni-ui` 的navbar标题栏、收藏、购物车等组件,均可实现自动打点,统计页面标题等各种行为数据。 +当然你也可以关闭uni统计,这不是强制的。 + +### 4. 主题扩展 + + `uni-ui` 支持[uni.scss](https://uniapp.dcloud.io/collocation/uni-scss),可以方便的切换App的风格。 + +ui是一种需求非常发散的产品,DCloud官方也无意用 `uni-ui` 压制第三方ui插件的空间,但官方有义务在性能和多端方面提供一个开源的标杆给大家。 + +我们欢迎更多优秀的ui组件出现,也欢迎更多人贡献 `uni-ui` 的主题风格,满足更多用户的需求。 + + +## 快速开始 + +uni-ui支持 HBuilderX直接新建项目模板、npm安装和单独导入个别组件等多种使用方式 + +### 在HBuilderX 新建uni-app项目的模板中,选择uni-ui模板 +![HBuilderX内创建uni-ui项目](https://img.cdn.aliyun.dcloud.net.cn/uni-app/doc/create-uni-ui-project.jpg) + +由于uni-app独特的[easycom](https://uniapp.dcloud.io/collocation/pages?id=easycom)技术,可以免引用、注册,直接使用各种符合规则的vue组件。 + +在代码区键入`u`,拉出各种内置或uni-ui的组件列表,选择其中一个,即可使用该组件。 + +光标放在组件名称上,按F1,可以查阅组件的文档。 + +![uni-ui代码块](https://img.cdn.aliyun.dcloud.net.cn/uni-app/doc/uni-ui-snippet.jpg) + +### 通过 uni_modules 单独安装组件 +如果你没有创建uni-ui项目模板,也可以在你的工程里,通过 uni_modules 单独安装需要的某个组件。下表为uni-ui的扩展组件清单,点击每个组件在详情页面可以导入组件到项目下,导入后直接使用即可,无需import和注册。 + +|组件名|组件说明| +|---|---| +|uni-badge|[数字角标](https://ext.dcloud.net.cn/plugin?name=uni-badge)| +|uni-calendar|[日历](https://ext.dcloud.net.cn/plugin?name=uni-calendar)| +|uni-card|[卡片](https://ext.dcloud.net.cn/plugin?name=uni-card)| +|uni-collapse|[折叠面板](https://ext.dcloud.net.cn/plugin?name=uni-collapse)| +|uni-combox|[组合框](https://ext.dcloud.net.cn/plugin?name=uni-combox)| +|uni-countdown|[倒计时](https://ext.dcloud.net.cn/plugin?name=uni-countdown)| +|uni-data-checkbox|[数据选择器](https://ext.dcloud.net.cn/plugin?name=uni-data-checkbox)| +|uni-data-picker|[数据驱动的picker选择器](https://ext.dcloud.net.cn/plugin?name=uni-data-picker)| +|uni-dateformat|[日期格式化](https://ext.dcloud.net.cn/plugin?name=uni-dateformat)| +|uni-datetime-picker|[日期选择器](https://ext.dcloud.net.cn/plugin?name=uni-datetime-picker)| +|uni-drawer|[抽屉](https://ext.dcloud.net.cn/plugin?name=uni-drawer)| +|uni-easyinput|[增强输入框](https://ext.dcloud.net.cn/plugin?name=uni-easyinput)| +|uni-fab|[悬浮按钮](https://ext.dcloud.net.cn/plugin?name=uni-fab)| +|uni-fav|[收藏按钮](https://ext.dcloud.net.cn/plugin?name=uni-fav)| +|uni-file-picker|[文件选择上传](https://ext.dcloud.net.cn/plugin?name=uni-file-picker)| +|uni-forms|[表单](https://ext.dcloud.net.cn/plugin?name=uni-forms)| +|uni-goods-nav|[商品导航](https://ext.dcloud.net.cn/plugin?name=uni-goods-nav)| +|uni-grid|[宫格](https://ext.dcloud.net.cn/plugin?name=uni-grid)| +|uni-group|[分组](https://ext.dcloud.net.cn/plugin?name=uni-group)| +|uni-icons|[图标](https://ext.dcloud.net.cn/plugin?name=uni-icons)| +|uni-indexed-list|[索引列表](https://ext.dcloud.net.cn/plugin?name=uni-indexed-list)| +|uni-link|[超链接](https://ext.dcloud.net.cn/plugin?name=uni-link)| +|uni-list|[列表](https://ext.dcloud.net.cn/plugin?name=uni-list)| +|uni-load-more|[加载更多](https://ext.dcloud.net.cn/plugin?name=uni-load-more)| +|uni-nav-bar|[自定义导航栏](https://ext.dcloud.net.cn/plugin?name=uni-nav-bar)| +|uni-notice-bar|[通告栏](https://ext.dcloud.net.cn/plugin?name=uni-notice-bar)| +|uni-number-box|[数字输入框](https://ext.dcloud.net.cn/plugin?name=uni-number-box)| +|uni-pagination|[分页器](https://ext.dcloud.net.cn/plugin?name=uni-pagination)| +|uni-popup|[弹出层](https://ext.dcloud.net.cn/plugin?name=uni-popup)| +|uni-rate|[评分](https://ext.dcloud.net.cn/plugin?name=uni-rate)| +|uni-row|[布局-行](https://ext.dcloud.net.cn/plugin?name=uni-row)| +|uni-search-bar|[搜索栏](https://ext.dcloud.net.cn/plugin?name=uni-search-bar)| +|uni-segmented-control|[分段器](https://ext.dcloud.net.cn/plugin?name=uni-segmented-control)| +|uni-steps|[步骤条](https://ext.dcloud.net.cn/plugin?name=uni-steps)| +|uni-swipe-action|[滑动操作](https://ext.dcloud.net.cn/plugin?name=uni-swipe-action)| +|uni-swiper-dot|[轮播图指示点](https://ext.dcloud.net.cn/plugin?name=uni-swiper-dot)| +|uni-table|[表格](https://ext.dcloud.net.cn/plugin?name=uni-table)| +|uni-tag|[标签](https://ext.dcloud.net.cn/plugin?name=uni-tag)| +|uni-title|[章节标题](https://ext.dcloud.net.cn/plugin?name=uni-title)| +|uni-transition|[过渡动画](https://ext.dcloud.net.cn/plugin?name=uni-transition)| + + +使用 `uni_modules` 方式安装组件库,可以直接通过插件市场导入,通过右键菜单快速更新组件,不需要引用、注册,直接在页面中使用 `uni-ui` 组件。[点击安装 uni-ui 组件库](https://ext.dcloud.net.cn/plugin?id=55) + +**注意:下载最新的组件目前仅支持 uni_modules ,非 uni_modules 版本最高支持到组件的1.2.10版本** + +如不能升级到 `uni_modules` 版本,可以使用 `uni_modules` 安装好对应组件,将组件拷贝到对应目录。 + +例如需更新 `uni-list`和`uni-badge` ,将 `uni_modules>uni-list>components`和`uni_modules>uni-badege>components`下所有目录拷贝到如下目录即可: + + +**目录示例** + +```json +┌─components 组件目录 +│ ├─uni-list list 列表目录 +│ │ └─uni-list.vue list 组件文件 +│ ├─uni-list-item list-item 列表目录 +│ │ └─uni-list-item.vue list 组件文件 +│ ├─uni-badge badge 角标目录 +│ │ └─uni-badge.vue badge 组件文件 +│ └─ //.... 更多组件文件 +├─pages 业务页面文件存放的目录 +│ ├─index +│ │ └─index.vue index示例页面 +├─main.js Vue初始化入口文件 +├─App.vue 应用配置,用来配置App全局样式以及监听 应用生命周期 +├─manifest.json 配置应用名称、appid、logo、版本等打包信息,详见 +└─pages.json 配置页 + +``` + +### 通过 `uni_modules` 导入全部组件 +如果想一次把所有uni-ui组件导入到项目中,只需要导入一个 `uni-ui` 组件即可 [点击去导入](https://ext.dcloud.net.cn/plugin?id=55)。 + +如果没有自动导入其他组件,可以在 uni-ui 组件目录上右键选择 `安装三方插件依赖` 即可。 + + + +### npm安装 +在 `vue-cli` 项目中可以使用 `npm` 安装 `uni-ui` 库 ,或者直接在 `HBuilderX` 项目中使用 `npm` 。 + +> **注意** +> cli 项目默认是不编译 `node_modules` 下的组件的,导致条件编译等功能失效 ,导致组件异常 +> 需要在根目录创建 `vue.config.js` 文件 ,增加 `@dcloudio/uni-ui` 包的编译即可正常 +> ```javascript +> // vue.config.js +> module.exports = { +> transpileDependencies:['@dcloudio/uni-ui'] +> } +> ``` + + + +**准备 sass** + +`vue-cli` 项目请先安装 sass 及 sass-loader,如在 HBuliderX 中使用,可跳过此步。 + +- 安装 sass +``` + npm i sass -D 或 yarn add sass -D +``` + +- 安装 sass-loader +``` +npm i sass-loader@10.1.1 -D 或 yarn add sass-loader@10.1.1 -D +``` + +> 如果 `node` 版本小于 16 ,sass-loader 请使用低于 @11.0.0 的版本,[sass-loader@11.0.0 不支持 vue@2.6.12 ](https://stackoverflow.com/questions/66082397/typeerror-this-getoptions-is-not-a-function) +> 如果 `node` 版本大于 16 , `sass-loader` 建议使用 `v8.x` 版本 + +**安装 uni-ui** + +``` +npm i @dcloudio/uni-ui 或 yarn add @dcloudio/uni-ui +``` + + + +**配置easycom** + +使用 `npm` 安装好 `uni-ui` 之后,需要配置 `easycom` 规则,让 `npm` 安装的组件支持 `easycom` + +打开项目根目录下的 `pages.json` 并添加 `easycom` 节点: + +```javascript +// pages.json +{ + "easycom": { + "autoscan": true, + "custom": { + // uni-ui 规则如下配置 + "^uni-(.*)": "@dcloudio/uni-ui/lib/uni-$1/uni-$1.vue" + } + }, + + // 其他内容 + pages:[ + // ... + ] +} + +``` + +在 ``template`` 中使用组件: + +```html + + + +``` + + **注意** + - uni-ui 现在只推荐使用 `easycom` ,如自己引用组件,可能会出现组件找不到的问题 + - 使用 npm 安装的组件,默认情况下 babel-loader 会忽略所有 node_modules 中的文件 ,导致条件编译失效,需要通过配置 `vue.config.js` 解决: + ```javascript + // 在根目录创建 vue.config.js 文件,并配置如下 + module.exports = { + transpileDependencies: ['@dcloudio/uni-ui'] + } + ``` + - uni-ui 是uni-app内置组件的扩展。注意与web开发不同,uni-ui不包括基础组件,它是基础组件的补充。web开发中有的开发者习惯用一个ui库完成所有开发,但在uni-app体系中,推荐开发者首先使用性能更高的基础组件,然后按需引入必要的扩展组件。 + - `uni-ui` 不支持使用 `Vue.use()` 的方式安装 + + +### 贡献代码 +在使用 `uni-ui` 中,如遇到无法解决的问题,请提 [Issues](https://github.com/dcloudio/uni-ui/issues) 给我们,假如您有更好的点子或更好的实现方式,也欢迎给我们提交 [PR](https://github.com/dcloudio/uni-ui/pulls) \ No newline at end of file