first submit
commit
2d1c7dd278
@ -0,0 +1,23 @@
|
||||
# See https://help.github.com/ignore-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
.env
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# css
|
||||
src/**/*.css
|
||||
|
||||
# compile files
|
||||
build
|
@ -0,0 +1,77 @@
|
||||
{
|
||||
// JSHint Default Configuration File (as on JSHint website)
|
||||
// See http://jshint.com/docs/ for more details
|
||||
|
||||
"maxerr" : 50, // {int} Maximum error before stopping
|
||||
|
||||
// Enforcing
|
||||
"bitwise" : true, // true: Prohibit bitwise operators (&, |, ^, etc.)
|
||||
"camelcase" : false, // true: Identifiers must be in camelCase
|
||||
"curly" : false, // true: Require {} for every new block or scope
|
||||
"eqeqeq" : true, // true: Require triple equals (===) for comparison
|
||||
"forin" : true, // true: Require filtering for..in loops with obj.hasOwnProperty()
|
||||
"freeze" : true, // true: prohibits overwriting prototypes of native objects such as Array, Date etc.
|
||||
"immed" : false, // true: Require immediate invocations to be wrapped in parens e.g. `(function () { } ());`
|
||||
"latedef" : false, // true: Require variables/functions to be defined before being used
|
||||
"newcap" : false, // true: Require capitalization of all constructor functions e.g. `new F()`
|
||||
"noarg" : true, // true: Prohibit use of `arguments.caller` and `arguments.callee`
|
||||
"noempty" : true, // true: Prohibit use of empty blocks
|
||||
"nonbsp" : true, // true: Prohibit "non-breaking whitespace" characters.
|
||||
"nonew" : false, // true: Prohibit use of constructors for side-effects (without assignment)
|
||||
"plusplus" : false, // true: Prohibit use of `++` and `--`
|
||||
"quotmark" : false, // Quotation mark consistency:
|
||||
// false : do nothing (default)
|
||||
// true : ensure whatever is used is consistent
|
||||
// "single" : require single quotes
|
||||
// "double" : require double quotes
|
||||
"undef" : true, // true: Require all non-global variables to be declared (prevents global leaks)
|
||||
"unused" : true, // Unused variables:
|
||||
// true : all variables, last function parameter
|
||||
// "vars" : all variables only
|
||||
// "strict" : all variables, all function parameters
|
||||
"strict" : true, // true: Requires all functions run in ES5 Strict Mode
|
||||
"maxparams" : false, // {int} Max number of formal params allowed per function
|
||||
"maxdepth" : false, // {int} Max depth of nested blocks (within functions)
|
||||
"maxstatements" : false, // {int} Max number statements per function
|
||||
"maxcomplexity" : false, // {int} Max cyclomatic complexity per function
|
||||
"maxlen" : false, // {int} Max number of characters per line
|
||||
"varstmt" : false, // true: Disallow any var statements. Only `let` and `const` are allowed.
|
||||
|
||||
// Relaxing
|
||||
"asi" : true, // true: Tolerate Automatic Semicolon Insertion (no semicolons)
|
||||
"boss" : false, // true: Tolerate assignments where comparisons would be expected
|
||||
"debug" : false, // true: Allow debugger statements e.g. browser breakpoints.
|
||||
"eqnull" : false, // true: Tolerate use of `== null`
|
||||
"esversion" : 6, // {int} Specify the ECMAScript version to which the code must adhere.
|
||||
"moz" : true, // true: Allow Mozilla specific syntax (extends and overrides esnext features)
|
||||
"esnext" : true,
|
||||
// (ex: `for each`, multiple try/catch, function expression…)
|
||||
"evil" : false, // true: Tolerate use of `eval` and `new Function()`
|
||||
"expr" : true, // true: Tolerate `ExpressionStatement` as Programs
|
||||
"funcscope" : false, // true: Tolerate defining variables inside control statements
|
||||
"globalstrict" : false, // true: Allow global "use strict" (also enables 'strict')
|
||||
"iterator" : false, // true: Tolerate using the `__iterator__` property
|
||||
"lastsemic" : false, // true: Tolerate omitting a semicolon for the last statement of a 1-line block
|
||||
"laxbreak" : false, // true: Tolerate possibly unsafe line breakings
|
||||
"laxcomma" : false, // true: Tolerate comma-first style coding
|
||||
"loopfunc" : false, // true: Tolerate functions being defined in loops
|
||||
"multistr" : false, // true: Tolerate multi-line strings
|
||||
"noyield" : false, // true: Tolerate generator functions with no yield statement in them.
|
||||
"notypeof" : false, // true: Tolerate invalid typeof operator values
|
||||
"proto" : false, // true: Tolerate using the `__proto__` property
|
||||
"scripturl" : false, // true: Tolerate script-targeted URLs
|
||||
"shadow" : false, // true: Allows re-define variables later in code e.g. `var x=1; x=2;`
|
||||
"sub" : false, // true: Tolerate using `[]` notation when it can still be expressed in dot notation
|
||||
"supernew" : false, // true: Tolerate `new function () { ... };` and `new Object;`
|
||||
"validthis" : false, // true: Tolerate using this in a non-constructor function
|
||||
|
||||
// Environments
|
||||
"devel" : true, // Development/debugging (alert, confirm, etc)
|
||||
"jquery" : true, // jQuery
|
||||
"node" : true, // Node.js
|
||||
"browser" : true, // Browser
|
||||
|
||||
|
||||
// Custom Globals
|
||||
"globals" : {} // additional predefined global variables
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
language: node_js
|
||||
|
||||
cache:
|
||||
directories:
|
||||
- node_modules
|
||||
- $HOME/.npm
|
||||
|
||||
notifications:
|
||||
email: false
|
||||
t
|
||||
node_js:
|
||||
- '8'
|
||||
|
||||
before_install:
|
||||
- npm i -g npm@^5.5.1
|
||||
|
||||
script:
|
||||
- npm run build
|
||||
- npm run test
|
||||
|
||||
after_success:
|
@ -0,0 +1,13 @@
|
||||
# rap2-dolores
|
||||
|
||||
[![JavaScript Style Guide](https://img.shields.io/badge/code_style-standard-brightgreen.svg)](https://standardjs.com)
|
||||
|
||||
RPA2 前端。
|
||||
|
||||
http://rap2.taobao.org/
|
||||
|
||||
## 本地开发
|
||||
|
||||
```
|
||||
npm run dev
|
||||
```
|
@ -0,0 +1,29 @@
|
||||
process.env.NODE_ENV = 'production'
|
||||
|
||||
// https://nodejs.org/api/cluster.html
|
||||
// https://github.com/node-modules/graceful/blob/master/example/express_with_cluster/dispatch.js
|
||||
// http://gitlab.alibaba-inc.com/mm/fb/blob/master/dispatch.js
|
||||
|
||||
let cluster = require('cluster')
|
||||
let path = require('path')
|
||||
let now = () => new Date().toISOString().replace(/T/, ' ').replace(/Z/, '')
|
||||
|
||||
cluster.setupMaster({
|
||||
exec: path.join(__dirname, 'scripts/worker.js')
|
||||
})
|
||||
|
||||
if (cluster.isMaster) {
|
||||
require('os').cpus().forEach((cpu, index) => {
|
||||
cluster.fork()
|
||||
})
|
||||
cluster.on('listening', (worker, address) => {
|
||||
console.error(`[${now()}] master#${process.pid} worker#${worker.process.pid} is now connected to ${address.address}:${address.port}.`)
|
||||
})
|
||||
cluster.on('disconnect', (worker) => {
|
||||
console.error(`[${now()}] master#${process.pid} worker#${worker.process.pid} has disconnected.`)
|
||||
})
|
||||
cluster.on('exit', (worker, code, signal) => {
|
||||
console.error(`[${now()}] master#${process.pid} worker#${worker.process.pid} died (${signal || code}). restarting...`)
|
||||
cluster.fork()
|
||||
})
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
{
|
||||
"name": "rap2-dolores",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"dev": "npm run start",
|
||||
"build-css": "node-sass src/ -o src/",
|
||||
"watch-css": "npm run build-css && node-sass src/ -o src/ --watch --recursive",
|
||||
"start-js": "react-scripts start",
|
||||
"start": "npm-run-all -p watch-css start-js",
|
||||
"build": "npm run build-css && react-scripts build",
|
||||
"test-backup": "npm run linter && react-scripts test --env=jsdom",
|
||||
"test": "npm run linter",
|
||||
"eject": "react-scripts eject",
|
||||
"linter": "standard --fix"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git@gitlab.alibaba-inc.com:thx/rap2-dolores.git"
|
||||
},
|
||||
"author": "mozhi.gyy@alibaba-inc.com",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"animate.css": "3.5.2",
|
||||
"bootstrap": "^4.0.0-beta.2",
|
||||
"chart.js": "^2.6.0",
|
||||
"codemirror": "5.27.2",
|
||||
"graceful": "1.0.1",
|
||||
"koa": "2.3.0",
|
||||
"koa-router": "7.2.1",
|
||||
"koa-session": "5.3.0",
|
||||
"koa-static": "3.0.0",
|
||||
"lodash": "4.17.4",
|
||||
"mockjs": "1.0.1-beta3",
|
||||
"moment": "2.18.1",
|
||||
"node-fetch": "1.7.1",
|
||||
"nprogress": "0.2.0",
|
||||
"parsleyjs": "^2.7.2",
|
||||
"prop-types": "15.5.10",
|
||||
"react": "15.6.1",
|
||||
"react-dom": "15.6.1",
|
||||
"react-icons": "2.2.5",
|
||||
"react-modal": "2.1.0",
|
||||
"react-redux": "5.0.5",
|
||||
"react-router": "4.1.1",
|
||||
"react-router-config": "1.0.0-beta.3",
|
||||
"react-router-dom": "4.1.1",
|
||||
"react-router-redux": "5.0.0-alpha.6",
|
||||
"redux": "3.7.1",
|
||||
"redux-saga": "0.15.4",
|
||||
"sortablejs": "1.6.0",
|
||||
"urijs": "1.18.10"
|
||||
},
|
||||
"standard": {
|
||||
"parser": "babel-eslint",
|
||||
"globals": [
|
||||
"fetch"
|
||||
],
|
||||
"ignore": [
|
||||
"/build"
|
||||
]
|
||||
},
|
||||
"engines": {
|
||||
"install-node": "9.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-eslint": "7.2.3",
|
||||
"node-sass": "4.5.3",
|
||||
"npm-run-all": "4.0.2",
|
||||
"pre-commit": "1.2.2",
|
||||
"react-scripts": "0.9.5",
|
||||
"standard": "10.0.2"
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 24 KiB |
Binary file not shown.
After Width: | Height: | Size: 1.1 KiB |
@ -0,0 +1,31 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.png">
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tag above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>RAP2 Delores</title>
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start`.
|
||||
To create a production bundle, use `npm run build`.
|
||||
-->
|
||||
</body>
|
||||
</html>
|
@ -0,0 +1,69 @@
|
||||
const fs = require('fs')
|
||||
const Koa = require('koa')
|
||||
const serve = require('koa-static')
|
||||
const Router = require('koa-router')
|
||||
const session = require('koa-session')
|
||||
const config = require('../src/config')
|
||||
const app = new Koa()
|
||||
|
||||
app.keys = config.keys
|
||||
app.use(session(config.session, app))
|
||||
|
||||
app.use(async (ctx, next) => {
|
||||
const start = new Date()
|
||||
await next()
|
||||
const ms = new Date() - start
|
||||
console.log(`${ctx.method} ${ctx.url} - ${ms}ms`)
|
||||
})
|
||||
|
||||
app.use(async (ctx, next) => {
|
||||
await next()
|
||||
if (ctx.response.body && ctx.response.body.url) {
|
||||
ctx.response.body = JSON.stringify(ctx.response.body, null, 4)
|
||||
}
|
||||
})
|
||||
|
||||
app.use(serve('build'))
|
||||
|
||||
let router = new Router()
|
||||
|
||||
router.get('/check.node', (ctx) => {
|
||||
ctx.body = 'success'
|
||||
})
|
||||
|
||||
router.get('/status.taobao', (ctx) => {
|
||||
ctx.body = 'success'
|
||||
})
|
||||
|
||||
router.get('/test/test.status', (ctx) => {
|
||||
ctx.body = 'success'
|
||||
})
|
||||
|
||||
router.get('/env', (ctx, next) => {
|
||||
ctx.body = process.env.NODE_ENV
|
||||
})
|
||||
|
||||
router.get('/delos', (ctx, next) => {
|
||||
ctx.body = process.env.RAP2_DELOS
|
||||
})
|
||||
|
||||
router.get('/account/info', (ctx) => {
|
||||
ctx.body = {
|
||||
url: ctx.request.url,
|
||||
data: ctx.session.id ? {
|
||||
id: ctx.session.id,
|
||||
empId: ctx.session.empId,
|
||||
fullname: ctx.session.fullname,
|
||||
email: ctx.session.email
|
||||
} : undefined
|
||||
}
|
||||
})
|
||||
|
||||
router.get('/*', (ctx) => {
|
||||
ctx.type = 'html'
|
||||
ctx.body = fs.createReadStream('build/index.html')
|
||||
})
|
||||
|
||||
app.use(router.routes())
|
||||
|
||||
module.exports = app
|
@ -0,0 +1,85 @@
|
||||
(*
|
||||
Copyright (c) 2015-present, Facebook, Inc.
|
||||
All rights reserved.
|
||||
|
||||
This source code is licensed under the BSD-style license found in the
|
||||
-- LICENSE file in the root directory of this source tree. An additional grant
|
||||
of patent rights can be found in the PATENTS file in the same directory.
|
||||
*)
|
||||
|
||||
property targetTab: null
|
||||
property targetTabIndex: -1
|
||||
property targetWindow: null
|
||||
|
||||
on run argv
|
||||
set theURL to item 1 of argv
|
||||
|
||||
tell application "Chrome"
|
||||
|
||||
if (count every window) = 0 then
|
||||
make new window
|
||||
end if
|
||||
|
||||
-- 1: Looking for tab running debugger
|
||||
-- then, Reload debugging tab if found
|
||||
-- then return
|
||||
set found to my lookupTabWithUrl(theURL)
|
||||
if found then
|
||||
set targetWindow's active tab index to targetTabIndex
|
||||
tell targetTab to reload
|
||||
tell targetWindow to activate
|
||||
set index of targetWindow to 1
|
||||
return
|
||||
end if
|
||||
|
||||
-- 2: Looking for Empty tab
|
||||
-- In case debugging tab was not found
|
||||
-- We try to find an empty tab instead
|
||||
set found to my lookupTabWithUrl("chrome://newtab/")
|
||||
if found then
|
||||
set targetWindow's active tab index to targetTabIndex
|
||||
set URL of targetTab to theURL
|
||||
tell targetWindow to activate
|
||||
return
|
||||
end if
|
||||
|
||||
-- 3: Create new tab
|
||||
-- both debugging and empty tab were not found
|
||||
-- make a new tab with url
|
||||
tell window 1
|
||||
activate
|
||||
make new tab with properties {URL:theURL}
|
||||
end tell
|
||||
end tell
|
||||
end run
|
||||
|
||||
-- Function:
|
||||
-- Lookup tab with given url
|
||||
-- if found, store tab, index, and window in properties
|
||||
-- (properties were declared on top of file)
|
||||
on lookupTabWithUrl(lookupUrl)
|
||||
tell application "Chrome"
|
||||
-- Find a tab with the given url
|
||||
set found to false
|
||||
set theTabIndex to -1
|
||||
repeat with theWindow in every window
|
||||
set theTabIndex to 0
|
||||
repeat with theTab in every tab of theWindow
|
||||
set theTabIndex to theTabIndex + 1
|
||||
if (theTab's URL as string) contains lookupUrl then
|
||||
-- assign tab, tab index, and window to properties
|
||||
set targetTab to theTab
|
||||
set targetTabIndex to theTabIndex
|
||||
set targetWindow to theWindow
|
||||
set found to true
|
||||
exit repeat
|
||||
end if
|
||||
end repeat
|
||||
|
||||
if found then
|
||||
exit repeat
|
||||
end if
|
||||
end repeat
|
||||
end tell
|
||||
return found
|
||||
end lookupTabWithUrl
|
@ -0,0 +1,17 @@
|
||||
// https://github.com/node-modules/graceful
|
||||
let graceful = require('graceful')
|
||||
let now = () => new Date().toISOString().replace(/T/, ' ').replace(/Z/, '')
|
||||
let app = require('./app')
|
||||
let PORT = 8080
|
||||
let server = app.listen(PORT, () => {
|
||||
console.log(`[${now()}] worker#${process.pid} rap2-dolores is running as ${PORT}`)
|
||||
})
|
||||
|
||||
graceful({
|
||||
servers: [server],
|
||||
killTimeout: '10s',
|
||||
error: (err, throwErrorCount) => {
|
||||
if (err.message) err.message += ` (uncaughtException throw ${throwErrorCount} times on pid:${process.pid})`
|
||||
console.error(`[${now()}] worker#${process.pid}] ${err.message}`)
|
||||
}
|
||||
})
|
@ -0,0 +1,57 @@
|
||||
// 登陆
|
||||
export const login = (user, onResolved) => ({ type: 'USER_LOGIN', user, onResolved })
|
||||
export const loginSucceeded = (user) => ({ type: 'USER_LOGIN_SUCCEEDED', user })
|
||||
export const loginFailed = (message) => ({ type: 'USER_LOGIN_FAILED', message })
|
||||
|
||||
// 登出
|
||||
export const logout = () => ({ type: 'USER_LOGOUT' })
|
||||
export const logoutSucceeded = () => ({ type: 'USER_LOGOUT_SUCCEEDED' })
|
||||
export const logoutFailed = () => ({ type: 'USER_LOGOUT_FAILED' })
|
||||
|
||||
// 获取登陆信息
|
||||
export const fetchLoginInfo = () => ({ type: 'USER_FETCH_LOGIN_INFO' })
|
||||
export const fetchLoginInfoSucceeded = (user) => ({ type: 'USER_FETCH_LOGIN_INFO_SUCCEEDED', user })
|
||||
export const fetchLoginInfoFailed = (message) => ({ type: 'USER_FETCH_LOGIN_INFO_FAILED', message })
|
||||
|
||||
// 注册
|
||||
export const addUser = (user, onResolved) => ({ type: 'USER_ADD', user, onResolved })
|
||||
export const addUserSucceeded = (user) => ({ type: 'USER_ADD_SUCCEEDED', user })
|
||||
export const addUserFailed = (message) => ({ type: 'USER_ADD_FAILED', message })
|
||||
|
||||
// 删除用户
|
||||
export const deleteUser = (id) => ({ type: 'USER_DELETE', id })
|
||||
export const deleteUserSucceeded = (id) => ({ type: 'USER_DELETE_SUCCEEDED', id })
|
||||
export const deleteUserFailed = (id) => ({ type: 'USER_DELETE_FAILED', id })
|
||||
|
||||
// 获取用户列表
|
||||
export const fetchUserCount = () => ({ type: 'USER_COUNT_FETCH' })
|
||||
export const fetchUserCountSucceeded = (count) => ({ type: 'USER_COUNT_FETCH_SUCCEEDED', count })
|
||||
export const fetchUserCountFailed = (message) => ({ type: 'USER_COUNT_FETCH_FAILED', message })
|
||||
export const fetchUserList = ({ cursor, limit } = {}) => ({ type: 'USER_LIST_FETCH', cursor, limit })
|
||||
export const fetchUserListSucceeded = (users) => ({ type: 'USER_LIST_FETCH_SUCCEEDED', users })
|
||||
export const fetchUserListFailed = (message) => ({ type: 'USER_LIST_FETCH_FAILED', message })
|
||||
|
||||
// 获取用户设置
|
||||
export const fetchSetting = () => ({ type: 'SETTING_FETCH' })
|
||||
export const fetchSettingSucceeded = (setting) => ({ type: 'SETTING_FETCH_SUCCEEDED', setting })
|
||||
export const fetchSettingFailed = (message) => ({ type: 'SETTING_FETCH_FAILED', message })
|
||||
|
||||
// 修改用户设置
|
||||
export const updateSetting = (setting) => ({ type: 'SETTING_UPDATE', setting })
|
||||
export const updateSettingSucceeded = (setting) => ({ type: 'SETTING_UPDATE', setting })
|
||||
export const updateSettingFailed = (message) => ({ type: 'SETTING_UPDATE', message })
|
||||
|
||||
// 获取用户通知
|
||||
export const fetchNotificationList = () => ({ type: 'NOTIFICATION_LIST_FETCH' })
|
||||
export const fetchNotificationListSucceeded = () => ({ type: 'NOTIFICATION_LIST_FETCH_SUCCEEDED' })
|
||||
export const fetchNotificationListFailed = () => ({ type: 'NOTIFICATION_LIST_FETCH_Failed' })
|
||||
|
||||
// 阅读用户通知
|
||||
export const readNotification = (id) => ({ type: 'NOTIFICATION_READ', id })
|
||||
export const readNotificationSucceeded = (id) => ({ type: 'NOTIFICATION_READ_SUCCEEDED', id })
|
||||
export const readNotificationFailed = (message) => ({ type: 'NOTIFICATION_READ_FAILED', message })
|
||||
|
||||
// 获取用户日志
|
||||
export const fetchLogList = ({ cursor, limit } = {}) => ({ type: 'LOG_LIST_FETCH', cursor, limit })
|
||||
export const fetchLogListSucceeded = (logs) => ({ type: 'LOG_LIST_FETCH_SUCCEEDED', logs })
|
||||
export const fetchLogListFailed = (message) => ({ type: 'LOG_LIST_FETCH_FAILED', message })
|
@ -0,0 +1,20 @@
|
||||
// 获取平台计数信息
|
||||
export const fetchCounter = () => ({ type: 'ANALYTICS_COUNTER_FETCH' })
|
||||
export const fetchCounterSucceeded = (counter) => ({ type: 'ANALYTICS_COUNTER_FETCH_SUCCEEDED', counter })
|
||||
export const fetchCounterFailed = (message) => ({ type: 'ANALYTICS_COUNTER_FETCH_FAILED', message })
|
||||
|
||||
export const fetchAnalyticsRepositoriesCreated = ({ start, end } = {}) => ({ type: 'ANALYTICS_REPOSITORIES_CREATED', start, end })
|
||||
export const fetchAnalyticsRepositoriesCreatedSucceeded = (analytics) => ({ type: 'ANALYTICS_REPOSITORIES_CREATED_SUCCEEDED', analytics })
|
||||
export const fetchAnalyticsRepositoriesCreatedFailed = (message) => ({ type: 'ANALYTICS_REPOSITORIES_CREATED_FAILED', message })
|
||||
|
||||
export const fetchAnalyticsRepositoriesUpdated = ({ start, end } = {}) => ({ type: 'ANALYTICS_REPOSITORIES_UPDATED', start, end })
|
||||
export const fetchAnalyticsRepositoriesUpdatedSucceeded = (analytics) => ({ type: 'ANALYTICS_REPOSITORIES_UPDATED_SUCCEEDED', analytics })
|
||||
export const fetchAnalyticsRepositoriesUpdatedFailed = (message) => ({ type: 'ANALYTICS_REPOSITORIES_UPDATED_FAILED', message })
|
||||
|
||||
export const fetchAnalyticsUsersActivation = ({ start, end } = {}) => ({ type: 'ANALYTICS_USERS_ACTIVATION', start, end })
|
||||
export const fetchAnalyticsUsersActivationSucceeded = (analytics) => ({ type: 'ANALYTICS_USERS_ACTIVATION_SUCCEEDED', analytics })
|
||||
export const fetchAnalyticsUsersActivationFailed = (message) => ({ type: 'ANALYTICS_USERS_ACTIVATION_FAILED', message })
|
||||
|
||||
export const fetchAnalyticsRepositoriesActivation = ({ start, end } = {}) => ({ type: 'ANALYTICS_REPOSITORIES_ACTIVATION', start, end })
|
||||
export const fetchAnalyticsRepositoriesActivationSucceeded = (analytics) => ({ type: 'ANALYTICS_REPOSITORIES_ACTIVATION_SUCCEEDED', analytics })
|
||||
export const fetchAnalyticsRepositoriesActivationFailed = (message) => ({ type: 'ANALYTICS_REPOSITORIES_ACTIVATION_FAILED', message })
|
@ -0,0 +1,27 @@
|
||||
export const addInterface = (itf, onResolved) => ({ type: 'INTERFACE_ADD', interface: itf, onResolved })
|
||||
export const addInterfaceSucceeded = (itf) => ({ type: 'INTERFACE_ADD_SUCCEEDED', interface: itf })
|
||||
export const addInterfaceFailed = (message) => ({ type: 'INTERFACE_ADD_FAILED', message })
|
||||
|
||||
export const updateInterface = (itf, onResolved) => ({ type: 'INTERFACE_UPDATE', interface: itf, onResolved })
|
||||
export const updateInterfaceSucceeded = (itf) => ({ type: 'INTERFACE_UPDATE_SUCCEEDED', interface: itf })
|
||||
export const updateInterfaceFailed = (message) => ({ type: 'INTERFACE_UPDATE_FAILED', message })
|
||||
|
||||
export const deleteInterface = (id, onResolved) => ({ type: 'INTERFACE_DELETE', id, onResolved })
|
||||
export const deleteInterfaceSucceeded = (id) => ({ type: 'INTERFACE_DELETE_SUCCEEDED', id })
|
||||
export const deleteInterfaceFailed = (message) => ({ type: 'INTERFACE_DELETE_FAILED', message })
|
||||
|
||||
export const fetchInterfaceCount = () => ({ type: 'INTERFACE_COUNT_FETCH' })
|
||||
export const fetchInterfaceCountSucceeded = (count) => ({ type: 'INTERFACE_COUNT_FETCH_SUCCEEDED', count })
|
||||
export const fetchInterfaceCountFailed = (message) => ({ type: 'INTERFACE_COUNT_FETCH_FAILED', message })
|
||||
|
||||
export const lockInterface = (id, onResolved) => ({ type: 'INTERFACE_LOCK', id, onResolved })
|
||||
export const lockInterfaceSucceeded = (id) => ({ type: 'INTERFACE_LOCK_SUCCEEDED', id })
|
||||
export const lockInterfaceFailed = (message) => ({ type: 'INTERFACE_LOCK_FAILED', message })
|
||||
|
||||
export const unlockInterface = (id, onResolved) => ({ type: 'INTERFACE_UNLOCK', id, onResolved })
|
||||
export const unlockInterfaceSucceeded = (itf) => ({ type: 'INTERFACE_UNLOCK_SUCCEEDED', interface: itf })
|
||||
export const unlockInterfaceFailed = (message) => ({ type: 'INTERFACE_UNLOCK_FAILED', message })
|
||||
|
||||
export const sortInterfaceList = (ids, onResolved) => ({ type: 'INTERFACE_LIST_SORT', ids, onResolved })
|
||||
export const sortInterfaceListSucceeded = (count) => ({ type: 'INTERFACE_LIST_SORT_SUCCEEDED', count })
|
||||
export const sortInterfaceListFailed = (message) => ({ type: 'INTERFACE_LIST_SORT_FAILED', message })
|
@ -0,0 +1,15 @@
|
||||
export const addModule = (module, onResolved) => ({ type: 'MODULE_ADD', module, onResolved })
|
||||
export const addModuleSucceeded = (module) => ({ type: 'MODULE_ADD_SUCCEEDED', module })
|
||||
export const addModuleFailed = (message) => ({ type: 'MODULE_ADD_FAILED', message })
|
||||
|
||||
export const updateModule = (module, onResolved) => ({ type: 'MODULE_UPDATE', module, onResolved })
|
||||
export const updateModuleSucceeded = (module) => ({ type: 'MODULE_UPDATE_SUCCEEDED', module })
|
||||
export const updateModuleFailed = (message) => ({ type: 'MODULE_UPDATE_FAILED', message })
|
||||
|
||||
export const deleteModule = (id, onResolved) => ({ type: 'MODULE_DELETE', id, onResolved })
|
||||
export const deleteModuleSucceeded = (id) => ({ type: 'MODULE_DELETE_SUCCEEDED', id })
|
||||
export const deleteModuleFailed = (message) => ({ type: 'MODULE_DELETE_FAILED', message })
|
||||
|
||||
export const sortModuleList = (ids, onResolved) => ({ type: 'MODULE_LIST_SORT', ids, onResolved })
|
||||
export const sortModuleListSucceeded = (count) => ({ type: 'MODULE_LIST_SORT_SUCCEEDED', count })
|
||||
export const sortModuleListFailed = (message) => ({ type: 'MODULE_LIST_SORT_FAILED', message })
|
@ -0,0 +1,31 @@
|
||||
export const addOrganization = (organization, onResolved) => ({ type: 'ORGANIZATION_ADD', organization, onResolved })
|
||||
export const addOrganizationSucceeded = (organization) => ({ type: 'ORGANIZATION_ADD_SUCCEEDED', organization })
|
||||
export const addOrganizationFailed = (message) => ({ type: 'ORGANIZATION_ADD_FAILED', message })
|
||||
|
||||
export const updateOrganization = (organization, onResolved) => ({ type: 'ORGANIZATION_UPDATE', organization, onResolved })
|
||||
export const updateOrganizationSucceeded = (organization) => ({ type: 'ORGANIZATION_UPDATE_SUCCEEDED', organization })
|
||||
export const updateOrganizationFailed = (message) => ({ type: 'ORGANIZATION_UPDATE_FAILED', message })
|
||||
|
||||
export const deleteOrganization = (id, onResolved) => ({ type: 'ORGANIZATION_DELETE', id, onResolved })
|
||||
export const deleteOrganizationSucceeded = (id) => ({ type: 'ORGANIZATION_DELETE_SUCCEEDED', id })
|
||||
export const deleteOrganizationFailed = (message) => ({ type: 'ORGANIZATION_DELETE_FAILED', message })
|
||||
|
||||
export const fetchOrganization = ({ id, organization } = {}) => ({ type: 'ORGANIZATION_FETCH', id, organization })
|
||||
export const fetchOrganizationSucceeded = (organization) => ({ type: 'ORGANIZATION_FETCH_SUCCEEDED', organization })
|
||||
export const fetchOrganizationFailed = (message) => ({ type: 'ORGANIZATION_FETCH_FAILED', message })
|
||||
|
||||
export const fetchOrganizationCount = () => ({ type: 'ORGANIZATION_COUNT_FETCH' })
|
||||
export const fetchOrganizationCountSucceeded = (count) => ({ type: 'ORGANIZATION_COUNT_FETCH_SUCCEEDED', count })
|
||||
export const fetchOrganizationCountFailed = (message) => ({ type: 'ORGANIZATION_COUNT_FETCH_FAILED', message })
|
||||
|
||||
export const fetchOwnedOrganizationList = ({ name } = {}) => ({ type: 'OWNED_ORGANIZATION_LIST_FETCH', name })
|
||||
export const fetchOwnedOrganizationListSucceeded = (organizations) => ({ type: 'OWNED_ORGANIZATION_LIST_FETCH_SUCCEEDED', organizations })
|
||||
export const fetchOwnedOrganizationListFailed = (message) => ({ type: 'OWNED_ORGANIZATION_LIST_FETCH_FAILED', message })
|
||||
|
||||
export const fetchJoinedOrganizationList = ({ name } = {}) => ({ type: 'JOINED_ORGANIZATION_LIST_FETCH', name })
|
||||
export const fetchJoinedOrganizationListSucceeded = (organizations) => ({ type: 'JOINED_ORGANIZATION_LIST_FETCH_SUCCEEDED', organizations })
|
||||
export const fetchJoinedOrganizationListFailed = (message) => ({ type: 'JOINED_ORGANIZATION_LIST_FETCH_FAILED', message })
|
||||
|
||||
export const fetchOrganizationList = ({ name, cursor, limit } = {}) => ({ type: 'ORGANIZATION_LIST_FETCH', name, cursor, limit })
|
||||
export const fetchOrganizationListSucceeded = (organizations) => ({ type: 'ORGANIZATION_LIST_FETCH_SUCCEEDED', organizations })
|
||||
export const fetchOrganizationListFailed = (message) => ({ type: 'ORGANIZATION_LIST_FETCH_FAILED', message })
|
@ -0,0 +1,19 @@
|
||||
export const addProperty = (property, onResolved) => ({ type: 'PROPERTY_ADD', property, onResolved })
|
||||
export const addPropertySucceeded = (property) => ({ type: 'PROPERTY_ADD_SUCCEEDED', property })
|
||||
export const addPropertyFailed = (message) => ({ type: 'PROPERTY_ADD_FAILED', message })
|
||||
|
||||
export const updateProperty = (property, onResolved) => ({ type: 'PROPERTY_UPDATE', property, onResolved })
|
||||
export const updatePropertySucceeded = (property) => ({ type: 'PROPERTY_UPDATE_SUCCEEDED', property })
|
||||
export const updatePropertyFailed = (message) => ({ type: 'PROPERTY_UPDATE_FAILED', message })
|
||||
|
||||
export const updateProperties = (itf, properties, onResolved) => ({ type: 'PROPERTIES_UPDATE', itf, properties, onResolved })
|
||||
export const updatePropertiesSucceeded = (properties) => ({ type: 'PROPERTIES_UPDATE_SUCCEEDED', properties })
|
||||
export const updatePropertiesFailed = (message) => ({ type: 'PROPERTIES_UPDATE_FAILED', message })
|
||||
|
||||
export const deleteProperty = (id, onResolved) => ({ type: 'PROPERTY_DELETE', id, onResolved })
|
||||
export const deletePropertySucceeded = (id) => ({ type: 'PROPERTY_DELETE_SUCCEEDED', id })
|
||||
export const deletePropertyFailed = (message) => ({ type: 'PROPERTY_DELETE_FAILED', message })
|
||||
|
||||
export const sortPropertyList = (ids, onResolved) => ({ type: 'PROPERTY_LIST_SORT', ids, onResolved })
|
||||
export const sortPropertyListSucceeded = (count) => ({ type: 'PROPERTY_LIST_SORT_SUCCEEDED', count })
|
||||
export const sortPropertyListFailed = (message) => ({ type: 'PROPERTY_LIST_SORT_FAILED', message })
|
@ -0,0 +1,33 @@
|
||||
export const addRepository = (repository, onResolved) => ({ type: 'REPOSITORY_ADD', repository, onResolved })
|
||||
export const addRepositorySucceeded = (repository) => ({ type: 'REPOSITORY_ADD_SUCCEEDED', repository })
|
||||
export const addRepositoryFailed = (message) => ({ type: 'REPOSITORY_ADD_FAILED', message })
|
||||
|
||||
export const updateRepository = (repository, onResolved) => ({ type: 'REPOSITORY_UPDATE', repository, onResolved })
|
||||
export const updateRepositorySucceeded = (repository) => ({ type: 'REPOSITORY_UPDATE_SUCCEEDED', repository })
|
||||
export const updateRepositoryFailed = (message) => ({ type: 'REPOSITORY_UPDATE_FAILED', message })
|
||||
|
||||
export const deleteRepository = (id) => ({ type: 'REPOSITORY_DELETE', id })
|
||||
export const deleteRepositorySucceeded = (id) => ({ type: 'REPOSITORY_DELETE_SUCCEEDED', id })
|
||||
export const deleteRepositoryFailed = (message) => ({ type: 'REPOSITORY_DELETE_FAILED', message })
|
||||
|
||||
export const fetchRepository = ({ id, repository } = {}) => ({ type: 'REPOSITORY_FETCH', id, repository })
|
||||
export const fetchRepositorySucceeded = (repository) => ({ type: 'REPOSITORY_FETCH_SUCCEEDED', repository })
|
||||
export const fetchRepositoryFailed = (message) => ({ type: 'REPOSITORY_FETCH_FAILED', message })
|
||||
|
||||
export const clearRepository = () => ({ type: 'REPOSITORY_CLEAR' })
|
||||
|
||||
export const fetchRepositoryCount = () => ({ type: 'REPOSITORY_COUNT_FETCH' })
|
||||
export const fetchRepositoryCountSucceeded = (count) => ({ type: 'REPOSITORY_COUNT_FETCH_SUCCEEDED', count })
|
||||
export const fetchRepositoryCountFailed = (message) => ({ type: 'REPOSITORY_COUNT_FETCH_FAILED', message })
|
||||
|
||||
export const fetchRepositoryList = ({ user, organization, name, cursor, limit } = {}) => ({ type: 'REPOSITORY_LIST_FETCH', user, organization, name, cursor, limit })
|
||||
export const fetchRepositoryListSucceeded = (repositories) => ({ type: 'REPOSITORY_LIST_FETCH_SUCCEEDED', repositories })
|
||||
export const fetchRepositoryListFailed = (message) => ({ type: 'REPOSITORY_LIST_FETCH_FAILED', message })
|
||||
|
||||
export const fetchOwnedRepositoryList = ({ user, name } = {}) => ({ type: 'OWNED_REPOSITORY_LIST_FETCH', user, name })
|
||||
export const fetchOwnedRepositoryListSucceeded = (repositories) => ({ type: 'OWNED_REPOSITORY_LIST_FETCH_SUCCEEDED', repositories })
|
||||
export const fetchOwnedRepositoryListFailed = (message) => ({ type: 'OWNED_REPOSITORY_LIST_FETCH_FAILED', message })
|
||||
|
||||
export const fetchJoinedRepositoryList = ({ user, name } = {}) => ({ type: 'JOINED_REPOSITORY_LIST_FETCH', user, name })
|
||||
export const fetchJoinedRepositoryListSucceeded = (repositories) => ({ type: 'JOINED_REPOSITORY_LIST_FETCH_SUCCEEDED', repositories })
|
||||
export const fetchJoinedRepositoryListFailed = (message) => ({ type: 'JOINED_REPOSITORY_LIST_FETCH_FAILED', message })
|
@ -0,0 +1,13 @@
|
||||
## 支持 Sasss
|
||||
|
||||
[Adding a CSS Preprocessor (Sass, Less etc.)](https://github.com/facebookincubator/create-react-app/blob/master/packages/react-scripts/template/README.md#adding-a-css-preprocessor-sass-less-etc)
|
||||
|
||||
## CSS 规约
|
||||
|
||||
[集团开发规约 - CSS 规约](http://groups.alidemo.cn/f2e-specs/style-guide/_book/1.coding/2.css-style-guide.html)
|
||||
[网易前端 - CSS 规范](http://nec.netease.com/standard/css-sort.html)
|
||||
[ecomfe - CSS编码规范](https://github.com/ecomfe/spec/blob/master/css-style-guide.md)
|
||||
|
||||
|
||||
## 参考
|
||||
* [常用的CSS命名规则](https://www.zhihu.com/question/19586885)
|
@ -0,0 +1,152 @@
|
||||
@import "./variables.sass";
|
||||
|
||||
// ----------------------------------------
|
||||
// 输入框 Input
|
||||
// 文本框 Textarea
|
||||
// ----------------------------------------
|
||||
|
||||
.form-control
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 10px;
|
||||
padding: 6px 9px;
|
||||
border: 1px solid $border;
|
||||
border-radius: 4px;
|
||||
box-shadow: none;
|
||||
font-size: 12px;
|
||||
background-color: white;
|
||||
&:focus
|
||||
border-color: $brand;
|
||||
outline: 0;
|
||||
|
||||
input.form-control,
|
||||
select.form-control
|
||||
height: 32px;
|
||||
|
||||
input[type="radio"],
|
||||
input[type="checkbox"]
|
||||
margin-top: 3px;
|
||||
|
||||
.form-horizontal
|
||||
label.control-label
|
||||
padding-top: 7px;
|
||||
padding-right: 0;
|
||||
label.radio-inline
|
||||
padding-top: 7px;
|
||||
padding-right: 0;
|
||||
margin: 0 15px 10px 0;
|
||||
input[type=radio]
|
||||
margin-right: 10px;
|
||||
|
||||
@media (max-width: 575px)
|
||||
.form-horizontal
|
||||
label.control-label
|
||||
text-align: left;
|
||||
|
||||
@media (min-width: 576px)
|
||||
.form-horizontal
|
||||
label.control-label
|
||||
text-align: right;
|
||||
|
||||
// ----------------------------------------
|
||||
// 按钮 Button
|
||||
// ----------------------------------------
|
||||
|
||||
.btn
|
||||
line-height: 1.5;
|
||||
.rapfont
|
||||
line-height: 1rem;
|
||||
margin-right: .5rem;
|
||||
a.btn.btn-success
|
||||
color: white;
|
||||
|
||||
// .btn
|
||||
// font-size: 12px;
|
||||
// line-height: 1.5;
|
||||
// padding: 6px 20px;
|
||||
// border: 1px solid $border;
|
||||
// border-radius: 4px;
|
||||
// background-color: $border;
|
||||
// cursor: pointer;
|
||||
// // height: 32px;
|
||||
// &:hover
|
||||
// background-color: #ccc;
|
||||
// border-color: $border;
|
||||
// &:focus
|
||||
// outline: none;
|
||||
// box-shadow: none;
|
||||
// &.btn-primary
|
||||
// color: white;
|
||||
// border-color: $brand;
|
||||
// background-color: $brand;
|
||||
// &:hover
|
||||
// background-color: $brand-hover;
|
||||
// &.btn-rapfont
|
||||
// padding-right: 2rem;
|
||||
// .rapfont
|
||||
// font-size: 1rem;
|
||||
// margin-right: 0.5rem;
|
||||
|
||||
// ----------------------------------------
|
||||
// 表格 Table
|
||||
// ----------------------------------------
|
||||
|
||||
table.table
|
||||
> thead
|
||||
> tr
|
||||
th
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
> tbody
|
||||
> tr
|
||||
> td
|
||||
vertical-align: middle;
|
||||
.operation
|
||||
visibility: hidden;
|
||||
> tr:hover
|
||||
> td
|
||||
.operation
|
||||
visibility: visible;
|
||||
|
||||
// ----------------------------------------
|
||||
// 面板 Panel
|
||||
// ----------------------------------------
|
||||
|
||||
// .panel
|
||||
// margin-bottom: 1rem;
|
||||
// border: 1px solid $border;
|
||||
// border-radius: 0.3rem;
|
||||
// > .panel-header,
|
||||
// > .panel-footer
|
||||
// padding: 0.75rem 1.25rem;
|
||||
// background-color: $bg;
|
||||
// > .panel-header
|
||||
// border-bottom: 1px solid $border;
|
||||
// > .panel-footer
|
||||
// border-top: 1px solid $border;
|
||||
// > .panel-body
|
||||
// padding: 1.25rem;
|
||||
|
||||
// ----------------------------------------
|
||||
// 整站开屏动画
|
||||
// ----------------------------------------
|
||||
|
||||
.OpeningScreenAdvertising
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 20rem;
|
||||
height: 4rem;
|
||||
margin-left: -10rem;
|
||||
margin-top: -2rem;
|
||||
.Spin
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
|
||||
// card
|
||||
.card-block
|
||||
-webkit-box-flex: 1;
|
||||
-ms-flex: 1 1 auto;
|
||||
flex: 1 1 auto;
|
||||
padding: 1.25rem;
|
@ -0,0 +1,25 @@
|
||||
// ----------------------------------------
|
||||
// 字体
|
||||
// ----------------------------------------
|
||||
|
||||
@font-face
|
||||
font-family: 'brixfont'; /* project id 12401 */
|
||||
src: url('//at.alicdn.com/t/font_p7lto2nib7h6ko6r.eot');
|
||||
src: url('//at.alicdn.com/t/font_p7lto2nib7h6ko6r.eot?#iefix') format('embedded-opentype'), url('//at.alicdn.com/t/font_p7lto2nib7h6ko6r.woff') format('woff'), url('//at.alicdn.com/t/font_p7lto2nib7h6ko6r.ttf') format('truetype'), url('//at.alicdn.com/t/font_p7lto2nib7h6ko6r.svg#iconfont') format('svg');
|
||||
.brixfont
|
||||
font-family: "brixfont" !important;
|
||||
|
||||
@font-face
|
||||
font-family: 'rapfont'; /* project id 182314 */
|
||||
src: url('//at.alicdn.com/t/font_afztm7sue48wipb9.eot');
|
||||
src: url('//at.alicdn.com/t/font_afztm7sue48wipb9.eot?#iefix') format('embedded-opentype'), url('//at.alicdn.com/t/font_afztm7sue48wipb9.woff') format('woff'), url('//at.alicdn.com/t/font_afztm7sue48wipb9.ttf') format('truetype'), url('//at.alicdn.com/t/font_afztm7sue48wipb9.svg#rapfont') format('svg');
|
||||
.rapfont
|
||||
font-family: "rapfont" !important;
|
||||
|
||||
|
||||
@font-face
|
||||
font-family: 'techfont';
|
||||
src: url('//g.alicdn.com/mm/zuanshi/20161214.111214.492/app/lib/Oswald-Bold.ttf');
|
||||
src: url('//g.alicdn.com/mm/zuanshi/20161214.111214.492/app/lib/Oswald-Bold.ttf?#iefix') format('embedded-opentype'), url('//g.alicdn.com/mm/zuanshi/20161214.111214.492/app/lib/Oswald-Bold.ttf') format('woff'), url('//g.alicdn.com/mm/zuanshi/20161214.111214.492/app/lib/Oswald-Bold.ttf') format('truetype'), url('//g.alicdn.com/mm/zuanshi/20161214.111214.492/app/lib/Oswald-Bold.ttf#FZZDHJW--GB1-0') format('svg');
|
||||
.techfont
|
||||
font-family: 'techfont';
|
@ -0,0 +1,103 @@
|
||||
@import "./variables.sass";
|
||||
|
||||
// ----------------------------------------
|
||||
// 布局
|
||||
// ----------------------------------------
|
||||
|
||||
.hide
|
||||
display: none !important;
|
||||
|
||||
// ----------------------------------------
|
||||
// 功能
|
||||
// ----------------------------------------
|
||||
|
||||
.fake-link
|
||||
color: $brand;
|
||||
cursor: pointer;
|
||||
&:hover
|
||||
color: $brand;
|
||||
|
||||
// ----------------------------------------
|
||||
// 颜色
|
||||
// ----------------------------------------
|
||||
|
||||
// 文字灰度
|
||||
.color-3 { color: #333; }
|
||||
.color-6 { color: #666; }
|
||||
.color-9 { color: #999; }
|
||||
.color-c { color: #CCC; }
|
||||
.color-f { color: #FFF; }
|
||||
|
||||
// 色块灰度
|
||||
.bg-c { background-color: #CCCCCC; }
|
||||
.bg-e6 { background-color: #E6E6E6; }
|
||||
.bg-f0 { background-color: #F0F0F0; }
|
||||
.bg-f5 { background-color: #F5F5F5; }
|
||||
.bg-fa { background-color: #FAFAFA; }
|
||||
.bg-f { background-color: #FFFFFF; }
|
||||
|
||||
// ----------------------------------------
|
||||
// 字体
|
||||
// ----------------------------------------
|
||||
|
||||
@each $i in 12,14,16,18,20,22,24,26,28,30,32,34,36,38,40,60
|
||||
.fontsize-#{$i}
|
||||
font-size: $i * 1px;
|
||||
|
||||
.font-bold
|
||||
font-weight: bold;
|
||||
.font-tahoma,
|
||||
.font-number
|
||||
font-family: "Tahoma";
|
||||
.nowrap
|
||||
white-space: nowrap;
|
||||
|
||||
// ----------------------------------------
|
||||
// 行距 Line Height
|
||||
// ----------------------------------------
|
||||
|
||||
@each $i in 0,1,2,3,4,5,6,7,8,9,10,12,15,20,25,30,32,35,40,50,60,80,100
|
||||
.m#{$i}
|
||||
margin: $i * 1px;
|
||||
.ml#{$i}
|
||||
margin-left: $i * 1px;
|
||||
.mr#{$i}
|
||||
margin-right: $i * 1px;
|
||||
.mt#{$i}
|
||||
margin-top: $i * 1px;
|
||||
.mb#{$i}
|
||||
margin-bottom: $i * 1px;
|
||||
.p#{$i}
|
||||
padding: $i * 1px;
|
||||
.pl#{$i}
|
||||
padding-left: $i * 1px;
|
||||
.pr#{$i}
|
||||
padding-right: $i * 1px;
|
||||
.pt#{$i}
|
||||
padding-top: $i * 1px;
|
||||
.pb#{$i}
|
||||
padding-bottom: $i * 1px;
|
||||
|
||||
@each $i in 50,60,70,80,90,100,110,120,130,140,150,160,170,180,190,200,210,220,230,240,250,260,270,280,290,300,310,320,340,350,360,370,380,390,400,500,600,800
|
||||
.w#{$i}
|
||||
width: $i * 1px;
|
||||
.h#{$i}
|
||||
height: $i * 1px;
|
||||
|
||||
// ----------------------------------------
|
||||
// 动画 Animation
|
||||
// ----------------------------------------
|
||||
|
||||
// .animated {
|
||||
// -webkit-animation-duration: 1s;
|
||||
// -moz-animation-duration: 1s;
|
||||
// animation-duration: 1s;
|
||||
// -webkit-animation-fill-mode: both;
|
||||
// -moz-animation-fill-mode: both;
|
||||
// animation-fill-mode: both;
|
||||
// }
|
||||
// .animated.infinite {
|
||||
// -webkit-animation-iteration-count: infinite;
|
||||
// -moz-animation-iteration-count: infinite;
|
||||
// animation-iteration-count: infinite;
|
||||
// }
|
@ -0,0 +1,25 @@
|
||||
// ----------------------------------------
|
||||
// 颜色
|
||||
// ----------------------------------------
|
||||
|
||||
$brand: #4A7BF7;
|
||||
$brand-hover: #4471E3;
|
||||
$nav-bg: #24292e;
|
||||
$nav-color: rgba(255, 255, 255, 0.75);
|
||||
$nav-hover: rgba(255, 255, 255, 1);
|
||||
$table-hover: #F0F3FA;
|
||||
$table-th-bg: #FAFAFA;
|
||||
|
||||
$bg: #F0F0F0;
|
||||
$border: #d1d5da;
|
||||
$warning: #FFB400;
|
||||
$danger: #A62A22;
|
||||
$success: #6CB12A;
|
||||
$fail: #A62A22;
|
||||
|
||||
|
||||
// ----------------------------------------
|
||||
// 字体
|
||||
// ----------------------------------------
|
||||
|
||||
$font-family: "Microsoft YaHei", "微软雅黑", STXihei, "华文细黑", Georgia, "Times New Roman", Arial, sans-serif;
|
@ -0,0 +1,17 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { renderRoutes } from 'react-router-config'
|
||||
|
||||
const Account = ({ route, match, location }) => (
|
||||
<article>
|
||||
{route && renderRoutes(route.routes)}
|
||||
</article>
|
||||
)
|
||||
|
||||
const mapStateToProps = (state) => ({})
|
||||
const mapDispatchToProps = ({})
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Account)
|
@ -0,0 +1,22 @@
|
||||
@import "../../assets/variables.sass";
|
||||
// 水平居中 + 垂直居中
|
||||
.LoginForm
|
||||
position: fixed;
|
||||
left: 50%;
|
||||
top: 50%;
|
||||
width: 25rem;
|
||||
margin-left: -12.5rem;
|
||||
margin-top: -135px;
|
||||
border: 1px solid #E6E6E6;
|
||||
border-radius: .5rem;
|
||||
.header
|
||||
padding: 1.5rem 3rem;
|
||||
border-bottom: 1px solid $border;
|
||||
.title
|
||||
font-size: 2rem;
|
||||
.body
|
||||
padding: 1.5rem 3rem;
|
||||
.footer
|
||||
padding: 1.5rem 3rem;
|
||||
border-top: 1px solid $border;
|
||||
|
@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { connect } from 'react-redux'
|
||||
|
||||
const Nav = ({ route, match, location }) => (
|
||||
<ul className='rap-navigation'>
|
||||
<li><NavLink to='/account/users' activeClassName='selected'>User List</NavLink></li>
|
||||
<li><NavLink to='/account/signin' activeClassName='selected'>Sign In</NavLink></li>
|
||||
<li><NavLink to='/account/signup' activeClassName='selected'>Sign Up</NavLink></li>
|
||||
</ul>
|
||||
)
|
||||
|
||||
const mapStateToProps = (state) => ({})
|
||||
const mapDispatchToProps = ({})
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Nav)
|
@ -0,0 +1,9 @@
|
||||
.RegisterForm
|
||||
width: 30rem;
|
||||
margin: 0 auto;
|
||||
.header
|
||||
margin-bottom: 3rem;
|
||||
.title
|
||||
font-size: 2rem;
|
||||
.body
|
||||
.footer
|
@ -0,0 +1,15 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
|
||||
const User = ({ match, id, fullname, email, onDeleteUser }) => (
|
||||
<tr>
|
||||
<td>{id}</td>
|
||||
<td>{fullname}</td>
|
||||
<td>{email}</td>
|
||||
<td>
|
||||
<Link to={match.url} onClick={e => onDeleteUser(id)}>X</Link>
|
||||
</td>
|
||||
</tr>
|
||||
)
|
||||
|
||||
export default User
|
@ -0,0 +1,66 @@
|
||||
import React from 'react'
|
||||
import { Link } from 'react-router-dom'
|
||||
import { connect } from 'react-redux'
|
||||
import Pagination from '../utils/Pagination'
|
||||
import { addUser, deleteUser, fetchUserList } from '../../actions/account'
|
||||
import './UserList.css'
|
||||
|
||||
// 展示组件
|
||||
const UserList = ({ history, match, location, users, onAddUser, onDeleteUser, onFetchUserList, tmpl = {} }) => (
|
||||
<section className='UserList'>
|
||||
<div className='header'>
|
||||
<span className='title'>用户管理</span>
|
||||
</div>
|
||||
<nav className='toolbar clearfix'>
|
||||
{/* <div className='float-left'>
|
||||
<Link to='/account/register' className='btn btn-success w140'><span className=''></span>注册用户</Link>
|
||||
</div>
|
||||
<div className='float-right'>
|
||||
<button onClick={e => onFetchUserList(location.params)} className='btn btn-default'>R</button>
|
||||
</div> */}
|
||||
</nav>
|
||||
<div className='body'>
|
||||
<table className='table'>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>姓名</th>
|
||||
<th>邮箱</th>
|
||||
<th className='w100'>操作</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{users.data.map(user =>
|
||||
<tr key={user.id}>
|
||||
<td>
|
||||
<Link to={`/repository?user=${user.id}`}>#{user.id} {user.fullname}</Link>
|
||||
</td>
|
||||
<td>{user.email}</td>
|
||||
<td>
|
||||
<span style={{ cursor: 'not-allowed' }}>
|
||||
<Link to={match.url} onClick={e => onDeleteUser(user.id)} className='operation disabled'>删除</Link>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div className='footer'>
|
||||
<Pagination location={location} calculated={users.pagination} />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
|
||||
// 容器组件
|
||||
const mapStateToProps = (state) => ({
|
||||
users: state.users
|
||||
})
|
||||
const mapDispatchToProps = ({
|
||||
onAddUser: addUser,
|
||||
onDeleteUser: deleteUser,
|
||||
onFetchUserList: fetchUserList
|
||||
})
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(UserList)
|
@ -0,0 +1,11 @@
|
||||
.UserList
|
||||
padding: 2rem;
|
||||
.header
|
||||
margin-bottom: 2rem;
|
||||
.title
|
||||
font-size: 2rem;
|
||||
.toolbar
|
||||
margin-bottom: 1rem;
|
||||
.body
|
||||
.table
|
||||
.footer
|
@ -0,0 +1,23 @@
|
||||
.APIList
|
||||
padding: 2rem;
|
||||
> .header
|
||||
margin-bottom: 2rem;
|
||||
.title
|
||||
font-size: 2rem;
|
||||
> .body
|
||||
> .API
|
||||
margin-bottom: 2rem;
|
||||
> .title
|
||||
font-size: 1.6rem;
|
||||
margin-bottom: 1rem;
|
||||
> ul
|
||||
padding-left: 2rem;
|
||||
li
|
||||
margin-bottom: .75rem;
|
||||
.label
|
||||
margin-right: .5rem;
|
||||
code
|
||||
padding: .5rem;
|
||||
.code-example
|
||||
padding: 2rem;
|
||||
margin: 2rem;
|
@ -0,0 +1,24 @@
|
||||
@import "../../assets/variables.sass";
|
||||
|
||||
.Checker
|
||||
padding: 2rem;
|
||||
.card-mods, .card-itfs
|
||||
.card-title
|
||||
float: left;
|
||||
width: 5rem;
|
||||
font-size: 1.4rem;
|
||||
a
|
||||
float: left;
|
||||
margin-right: 1rem;
|
||||
width: 10rem;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
color: #666;
|
||||
a.active
|
||||
color: $brand;
|
||||
.card-result
|
||||
margin-top: 1rem;
|
||||
.card-title
|
||||
font-size: 1.4rem;
|
||||
margin-bottom: 1rem;
|
@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import './Footer.css'
|
||||
|
||||
const Footer = ({ counter = {} }) => (
|
||||
<div className='Footer'>
|
||||
v{counter.version}
|
||||
{/* <span className='ml10 mr10 color-c'>|</span>
|
||||
{counter.users} 人正在使用 RAP
|
||||
<span className='ml10 mr10 color-c'>|</span>
|
||||
今日 Mock 服务被调用 {counter.mock} 次 */}
|
||||
<ul className='friend_links'>
|
||||
<li><a href='http://rap.alibaba-inc.com/' target='_blank' rel='noopener noreferrer'>RAP0.x</a></li>
|
||||
<li><a href='http://mockjs.com/' target='_blank' rel='noopener noreferrer'>Mock.js</a></li>
|
||||
<li><a href='https://thx.github.io/' target='_blank' rel='noopener noreferrer'>THX</a></li>
|
||||
<li><a href='https://fe.alimama.net/thx/30/' target='_blank' rel='noopener noreferrer'>MMFE</a></li>
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
counter: state.counter
|
||||
})
|
||||
const mapDispatchToProps = ({})
|
||||
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Footer)
|
@ -0,0 +1,15 @@
|
||||
@import "../../assets/variables.sass";
|
||||
|
||||
.Footer
|
||||
margin-top: 2rem;
|
||||
padding: 2rem 1rem 1rem 1rem;
|
||||
border-top: 1px solid $border;
|
||||
text-align: center;
|
||||
ul.friend_links
|
||||
margin-top: .5rem;
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
li
|
||||
display: inline-block;
|
||||
margin-right: 1rem;
|
||||
|
@ -0,0 +1,38 @@
|
||||
@import "../../assets/variables.sass";
|
||||
|
||||
.Header
|
||||
background-color: $nav-bg;
|
||||
color: white;
|
||||
font-size: 1.4rem;
|
||||
line-height: 4rem;
|
||||
vertical-align: middle;
|
||||
// margin-bottom: 2rem;
|
||||
padding: 0 2rem;
|
||||
ul.nav-links
|
||||
float: left;
|
||||
ul.nav-actions
|
||||
float: right;
|
||||
ul.nav-links,
|
||||
ul.nav-actions
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
> li
|
||||
float: left;
|
||||
padding: 0 1rem;
|
||||
> a
|
||||
color: white;
|
||||
opacity: 0.5;
|
||||
&:hover, &.selected
|
||||
border: none;
|
||||
font-weight: bold;
|
||||
opacity: 1;
|
||||
> a.logo
|
||||
opacity: 1;
|
||||
font-weight: bold;
|
||||
.avatar
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
.name
|
||||
margin-left: .5rem;
|
@ -0,0 +1,19 @@
|
||||
import React from 'react'
|
||||
import { NavLink } from 'react-router-dom'
|
||||
import { GoHome, GoRepo, GoOrganization, GoPulse, GoPlug } from 'react-icons/lib/go'
|
||||
|
||||
export default () => (
|
||||
<ul className='nav-links'>
|
||||
<li><NavLink exact to='/' activeClassName='selected'><GoHome /> 首页</NavLink></li>
|
||||
<li><NavLink to='/repository' activeClassName='selected'><GoRepo /> 仓库</NavLink></li>
|
||||
<li><NavLink to='/organization' activeClassName='selected'><GoOrganization /> 团队</NavLink></li>
|
||||
<li><NavLink to='/api' activeClassName='selected'><GoPlug /> 接口</NavLink></li>
|
||||
<li><NavLink to='/status' activeClassName='selected'><GoPulse /> 状态</NavLink></li>
|
||||
{/* <li><NavLink to='/manage' activeClassName='selected'>管理</NavLink></li> */}
|
||||
{/* <li><NavLink to='/account' activeClassName='selected'>Users</NavLink></li>
|
||||
<li><NavLink to='/organization' activeClassName='selected'>Organization</NavLink></li>
|
||||
<li><NavLink to='/workspace' activeClassName='selected'>Workspace</NavLink></li>
|
||||
<li><NavLink to='/analytics' activeClassName='selected'>Analytics</NavLink></li>
|
||||
<li><NavLink to='/utils' activeClassName='selected'>Utils</NavLink></li> */}
|
||||
</ul>
|
||||
)
|
@ -0,0 +1,9 @@
|
||||
@import "../../assets/variables.sass";
|
||||
|
||||
#nprogress
|
||||
.bar
|
||||
background-color: $brand;
|
||||
.spinner
|
||||
.spinner-icon
|
||||
border-top-color: $brand;
|
||||
border-left-color: $brand;
|
@ -0,0 +1,105 @@
|
||||
import React, { Component } from 'react'
|
||||
import { PropTypes, Link, Mock, _ } from '../../family'
|
||||
import { Tree } from '../utils'
|
||||
import { serve } from '../../relatives/services/constant'
|
||||
import { GoLink, GoSync, GoBeer, GoBug } from 'react-icons/lib/go'
|
||||
import { RE_KEY } from 'mockjs/src/mock/constant'
|
||||
|
||||
class Previewer extends Component {
|
||||
static propTypes = {
|
||||
label: PropTypes.string.isRequired,
|
||||
scope: PropTypes.string.isRequired,
|
||||
properties: PropTypes.array.isRequired,
|
||||
itf: PropTypes.object.isRequired
|
||||
}
|
||||
render () {
|
||||
let { label, scope, properties, itf } = this.props
|
||||
|
||||
// DONE 2.2 支持引用请求参数
|
||||
let scopedProperties = {
|
||||
request: properties.map(property => ({ ...property })).filter(property => property.scope === 'request'),
|
||||
response: properties.map(property => ({ ...property })).filter(property => property.scope === 'response')
|
||||
}
|
||||
let scopedTemplate = {
|
||||
request: Tree.treeToJson(Tree.arrayToTree(scopedProperties.request)),
|
||||
response: Tree.treeToJson(Tree.arrayToTree(scopedProperties.response))
|
||||
}
|
||||
let scopedKeys = {
|
||||
request: Object.keys(scopedTemplate.request).map(item => item.replace(RE_KEY, '$1')),
|
||||
response: Object.keys(scopedTemplate.response).map(item => item.replace(RE_KEY, '$1'))
|
||||
}
|
||||
let extraKeys = _.difference(scopedKeys.request, scopedKeys.response)
|
||||
let scopedData = {
|
||||
request: Mock.mock(scopedTemplate.request)
|
||||
}
|
||||
scopedData.response = Mock.mock(
|
||||
Object.assign({}, _.pick(scopedData.request, extraKeys), scopedTemplate.response)
|
||||
)
|
||||
scopedData.response = _.pick(scopedData.response, scopedKeys.response)
|
||||
|
||||
let template = scopedTemplate[scope]
|
||||
let data = scopedData[scope]
|
||||
|
||||
// DONE 2.1 支持虚拟属性 __root__ √服务端 √前端 √迁移测试
|
||||
let keys = Object.keys(data)
|
||||
if (keys.length === 1 && keys[0] === '__root__') data = data.__root__
|
||||
|
||||
let { Assert } = Mock.valid
|
||||
let valid = Mock.valid(template, data)
|
||||
for (var i = 0; i < valid.length; i++) {
|
||||
console.warn(Assert.message(valid[i]))
|
||||
}
|
||||
return (
|
||||
<div className='Previewer row'>
|
||||
<div className='result-template col-6'>
|
||||
<div className='header'>
|
||||
<span className='title'>{label}模板</span>
|
||||
{scope === 'response'
|
||||
? <Link to={`${serve}/app/mock/template/${itf.id}`} target='_blank'><GoLink className='fontsize-14' /></Link>
|
||||
: null}
|
||||
</div>
|
||||
<pre className='body'>{
|
||||
JSON.stringify(template, (k, v) => {
|
||||
if (typeof v === 'function') return v.toString()
|
||||
if (v !== undefined && v !== null && v.exec) return v.toString()
|
||||
else return v
|
||||
}, 2)
|
||||
}</pre>
|
||||
</div>
|
||||
<div className='result-mocked col-6'>
|
||||
<div className='header'>
|
||||
<span className='title'>{label}数据</span>
|
||||
{scope === 'response'
|
||||
? <Link to={`${serve}/app/mock/data/${itf.id}`} target='_blank'><GoLink className='mr6 fontsize-14' /></Link>
|
||||
: null}
|
||||
<Link to='' onClick={e => this.remock(e)}><GoSync className='mr6 fontsize-14' onAnimationEnd={e => this.removeAnimateClass(e)} /></Link>
|
||||
</div>
|
||||
<pre className='body'>{JSON.stringify(data, null, 2)}</pre>
|
||||
</div>
|
||||
{scope === 'response'
|
||||
? <div className='result-valid col-12'>
|
||||
{!valid.length
|
||||
? <span><GoBeer className='mr6 fontsize-20' />模板与数据匹配 √</span>
|
||||
: <span><GoBug className='mr6 fontsize-20' />模板与数据不匹配</span>
|
||||
}
|
||||
</div>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
remock = (e) => {
|
||||
e.preventDefault()
|
||||
let target = e.currentTarget.firstChild
|
||||
target.classList.add('animated')
|
||||
target.classList.add('rotateIn')
|
||||
this.forceUpdate()
|
||||
}
|
||||
removeAnimateClass = (e) => {
|
||||
let target = e.currentTarget
|
||||
target.classList.remove('animated')
|
||||
target.classList.remove('rotateIn')
|
||||
}
|
||||
}
|
||||
|
||||
export default Previewer
|
@ -0,0 +1,126 @@
|
||||
import React, { Component } from 'react'
|
||||
import { PropTypes, connect, Link, replace, URI, StoreStateRouterLocationURI } from '../../family'
|
||||
import { RModal, RSortable } from '../utils'
|
||||
import ModuleForm from './ModuleForm'
|
||||
import { GoPencil, GoTrashcan, GoPackage } from 'react-icons/lib/go'
|
||||
|
||||
class Module extends Component {
|
||||
static propTypes = {
|
||||
auth: PropTypes.object.isRequired,
|
||||
repository: PropTypes.object.isRequired,
|
||||
mod: PropTypes.object.isRequired
|
||||
}
|
||||
static contextTypes = {
|
||||
store: PropTypes.object,
|
||||
onDeleteModule: PropTypes.func
|
||||
}
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = { update: false }
|
||||
}
|
||||
render () {
|
||||
let { store } = this.context
|
||||
let { auth, repository, mod } = this.props
|
||||
let uri = StoreStateRouterLocationURI(store).removeSearch('itf')
|
||||
let selectHref = URI(uri).setSearch('mod', mod.id).href()
|
||||
return (
|
||||
<div className='Module clearfix'>
|
||||
<Link to={selectHref} className='name'>{mod.name}</Link>
|
||||
<div className='toolbar'>
|
||||
{/* 编辑权限:拥有者或者成员 */}
|
||||
{repository.owner.id === auth.id || repository.members.find(itme => itme.id === auth.id)
|
||||
? <span className='fake-link' onClick={e => this.setState({ update: true })}><GoPencil /></span>
|
||||
: null
|
||||
}
|
||||
{repository.owner.id === auth.id || repository.members.find(itme => itme.id === auth.id)
|
||||
? <span className='fake-link' onClick={e => this.handleDelete(e, mod)}><GoTrashcan /></span>
|
||||
: null
|
||||
}
|
||||
</div>
|
||||
<RModal when={this.state.update} onClose={e => this.setState({ update: false })} onResolve={this.handleUpdate}>
|
||||
<ModuleForm title='修改模块' mod={mod} repository={repository} />
|
||||
</RModal>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
handleUpdate = (e) => {
|
||||
let { store } = this.context
|
||||
store.dispatch(replace(StoreStateRouterLocationURI(store).href()))
|
||||
}
|
||||
handleDelete = (e, mod) => {
|
||||
e.preventDefault()
|
||||
let message = `模块被删除后不可恢复,并且会删除相关的接口!\n确认继续删除『#${mod.id} ${mod.name}』吗?`
|
||||
if (window.confirm(message)) {
|
||||
this.context.onDeleteModule(this.props.mod.id, () => {
|
||||
let { store } = this.context
|
||||
let uri = StoreStateRouterLocationURI(store)
|
||||
let deleteHref = this.props.active ? URI(uri).removeSearch('mod').href() : uri.href()
|
||||
store.dispatch(replace(deleteHref))
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ModuleList extends Component {
|
||||
static contextTypes = {
|
||||
store: PropTypes.object.isRequired,
|
||||
onSortModuleList: PropTypes.func.isRequired
|
||||
}
|
||||
static propTypes = {
|
||||
auth: PropTypes.object.isRequired,
|
||||
repository: PropTypes.object.isRequired,
|
||||
mods: PropTypes.array,
|
||||
mod: PropTypes.object
|
||||
}
|
||||
static childContextTypes = {}
|
||||
getChildContext () {}
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = { create: false }
|
||||
}
|
||||
render () {
|
||||
let { auth, repository = {}, mods = [], mod = {} } = this.props
|
||||
let isOwned = repository.owner.id === auth.id
|
||||
let isJoined = repository.members.find(itme => itme.id === auth.id)
|
||||
return (
|
||||
<RSortable onChange={this.handleSort} disabled={!isOwned && !isJoined}>
|
||||
<ul className='ModuleList clearfix'>
|
||||
{mods.map((item, index) =>
|
||||
<li key={item.id} className={item.id === mod.id ? 'active sortable' : 'sortable'} data-id={item.id}>
|
||||
<Module key={item.id} mod={item} active={item.id === mod.id} repository={repository} auth={auth} />
|
||||
</li>
|
||||
)}
|
||||
{/* 编辑权限:拥有者或者成员 */}
|
||||
{isOwned || isJoined
|
||||
? <li>
|
||||
<span className='fake-link' onClick={e => this.setState({ create: true })}>
|
||||
<GoPackage className='fontsize-14' /> 新建模块
|
||||
</span>
|
||||
<RModal when={this.state.create} onClose={e => this.setState({ create: false })} onResolve={this.handleCreate}>
|
||||
<ModuleForm title='新建模块' repository={repository} />}
|
||||
</RModal>
|
||||
</li>
|
||||
: null
|
||||
}
|
||||
</ul>
|
||||
</RSortable>
|
||||
)
|
||||
}
|
||||
handleCreate = () => {
|
||||
let { store } = this.context
|
||||
store.dispatch(replace(StoreStateRouterLocationURI(store).href()))
|
||||
}
|
||||
handleSort = (e, sortable) => {
|
||||
let { onSortModuleList } = this.context
|
||||
onSortModuleList(sortable.toArray())
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
auth: state.auth
|
||||
})
|
||||
const mapDispatchToProps = ({})
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ModuleList)
|
@ -0,0 +1,368 @@
|
||||
@import "../../assets/variables.sass";
|
||||
|
||||
.RepositoryEditor
|
||||
> .header
|
||||
position: relative;
|
||||
padding: 2rem;
|
||||
background-color: #fafbfc;
|
||||
> .title
|
||||
font-size: 2rem;
|
||||
margin-right: 1rem;
|
||||
.slash
|
||||
color: #999;
|
||||
> .toolbar
|
||||
display: inline-block;
|
||||
a, .fake-link
|
||||
margin-right: 1rem;
|
||||
> .desc
|
||||
margin-top: .5rem;
|
||||
color: #666;
|
||||
> .body
|
||||
// padding: 0 2rem;
|
||||
|
||||
.RepositorySearcher.dropdown
|
||||
position: absolute;
|
||||
top: 2rem;
|
||||
right: 2rem;
|
||||
left: auto;
|
||||
> .dropdown-input
|
||||
margin-bottom: 0;
|
||||
width: 20rem;
|
||||
> .dropdown-menu
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: auto;
|
||||
display: block;
|
||||
min-width: 100%;
|
||||
max-height: 50rem;
|
||||
overflow: scroll;
|
||||
a.dropdown-item
|
||||
&.dropdown-item-module
|
||||
padding-left: 1.5rem;
|
||||
color: #333;
|
||||
&.dropdown-item-interface
|
||||
padding-left: 1.5rem + 2.5rem;
|
||||
color: #333;
|
||||
&.dropdown-item-property
|
||||
padding-left: 1.5rem + 2.5rem + 2.5rem;
|
||||
color: #666;
|
||||
|
||||
> .label
|
||||
margin-right: .5rem;
|
||||
color: #666;
|
||||
|
||||
> .dropdown-item-clip
|
||||
margin-right: .5rem;
|
||||
.highlight
|
||||
font-weight: bold;
|
||||
color: $brand;
|
||||
|
||||
&:active
|
||||
color: #FFF;
|
||||
> .label
|
||||
color: #FFF;
|
||||
> .dropdown-item-clip
|
||||
.highlight
|
||||
color: #FFF;
|
||||
|
||||
.DuplicatedInterfacesWarning
|
||||
margin-top: 1rem;
|
||||
.alert.alert-warning
|
||||
margin-bottom: .5rem;
|
||||
.title
|
||||
margin-right: 1rem;
|
||||
.icon
|
||||
font-size: 1.4rem;
|
||||
margin-right: .5rem;
|
||||
.msg
|
||||
font-weight: bold;
|
||||
margin-right: 1rem;
|
||||
.itf
|
||||
a
|
||||
margin-right: 1rem;
|
||||
|
||||
.ModuleList
|
||||
margin: 0 0 1.5rem 0;
|
||||
padding: 0 2rem;
|
||||
list-style: none;
|
||||
border-bottom: 1px solid #e1e4e8;
|
||||
background-color: #fafbfc;
|
||||
> li
|
||||
position: relative;
|
||||
display: block;
|
||||
float: left;
|
||||
margin-bottom: -1px;
|
||||
padding: .8rem 1.2rem;
|
||||
border: 1px solid transparent;
|
||||
border-width: 3px 1px 0px 1px;
|
||||
border-radius: .4rem .4rem 0 0;
|
||||
&.active
|
||||
// border: 1px solid $border;
|
||||
border-bottom-color: transparent;
|
||||
background-color: white;
|
||||
cursor: default;
|
||||
border-color: #e36209 #e1e4e8 transparent #e1e4e8;
|
||||
&.active:hover
|
||||
background-color: white;
|
||||
> .Module
|
||||
position: relative;
|
||||
a.name
|
||||
color: #586069;
|
||||
.toolbar
|
||||
// float: right;
|
||||
display: inline-block;
|
||||
a, .fake-link
|
||||
margin-left: 0.5rem;
|
||||
font-size: 1.4rem;
|
||||
color: #999;
|
||||
&:hover
|
||||
color: $brand;
|
||||
> li:hover > .Module
|
||||
.toolbar
|
||||
display: inline-block;
|
||||
> li.active > .Module
|
||||
a.name
|
||||
color: #333;
|
||||
.toolbar
|
||||
display: inline-block;
|
||||
// font-size: 1.4rem;
|
||||
|
||||
.InterfaceWrapper
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0 2rem;
|
||||
.InterfaceList
|
||||
flex-basis: 16rem;
|
||||
flex-shrink: 0;
|
||||
.InterfaceEditor
|
||||
flex-grow: 1;
|
||||
|
||||
.InterfaceList
|
||||
ul.body
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
border: 1px solid #d1d5da;
|
||||
border-radius: .4rem;
|
||||
list-style: none;
|
||||
> li
|
||||
position: relative;
|
||||
padding: 1rem 1rem;
|
||||
border-bottom: 1px solid #d1d5da;
|
||||
&:last-child
|
||||
border-bottom: 0;
|
||||
.Interface
|
||||
position: relative;
|
||||
padding-right: 4rem;
|
||||
.name
|
||||
position: relative;
|
||||
float: left;
|
||||
// width: 10rem;
|
||||
max-width: 30rem;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
.toolbar
|
||||
display: none;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
font-size: 1.4rem;
|
||||
a, .fake-link
|
||||
margin-left: 0.5rem;
|
||||
color: #999;
|
||||
&:hover
|
||||
color: $brand;
|
||||
.locked
|
||||
font-size: 1.4rem;
|
||||
color: $warning;
|
||||
> li:hover
|
||||
.toolbar
|
||||
display: block;
|
||||
> li.active
|
||||
.Interface
|
||||
.name a
|
||||
color: #333;
|
||||
&:hover
|
||||
color: #333;
|
||||
.toolbar
|
||||
display: block;
|
||||
> li.active
|
||||
&::before
|
||||
position: absolute;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 2px;
|
||||
content: "";
|
||||
background-color: #e36209;
|
||||
.footer
|
||||
padding: 0.75rem 1rem;
|
||||
|
||||
.InterfaceEditor
|
||||
margin-left: 2rem;
|
||||
position: relative;
|
||||
.InterfaceEditorToolbar
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
.btn.edit, .btn.save, .btn.cancel, .locker-success, .locker-warning
|
||||
display: block;
|
||||
margin-bottom: 0.5rem;
|
||||
width: 12rem;
|
||||
.locker-success
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1.5rem;
|
||||
border-radius: 0.4rem;
|
||||
color: white;
|
||||
background-color: $success;
|
||||
.locker-warning
|
||||
@extend .locker-success;
|
||||
background-color: $warning;
|
||||
|
||||
.InterfaceSummary
|
||||
margin-bottom: 2rem;
|
||||
padding-right: 13rem;
|
||||
> .header
|
||||
margin-bottom: .5rem;
|
||||
> .title
|
||||
font-size: 1.6rem;
|
||||
margin-right: 1rem;
|
||||
.slash
|
||||
color: #999;
|
||||
a.edit, a.delete
|
||||
margin-right: .5rem;
|
||||
ul.body
|
||||
color: #666;
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
list-style: none;
|
||||
> li
|
||||
margin-bottom: .2rem;
|
||||
.label
|
||||
color: #666;
|
||||
margin-right: .3rem;
|
||||
|
||||
.PropertyList
|
||||
margin-bottom: 3rem;
|
||||
> .header
|
||||
margin-bottom: 1rem;
|
||||
> .title
|
||||
font-size: 1.6rem;
|
||||
margin-right: 1rem;
|
||||
> .toolbar
|
||||
float: right;
|
||||
.preview
|
||||
margin: 0;
|
||||
input
|
||||
margin-right: .5rem;
|
||||
> .body
|
||||
margin-bottom: 1rem;
|
||||
> .footer
|
||||
> .Previewer
|
||||
margin-top: 1rem;
|
||||
> .result-template,
|
||||
> .result-mocked
|
||||
> .header
|
||||
margin-bottom: .5rem;
|
||||
.title
|
||||
margin-right: .75rem
|
||||
> pre.body
|
||||
> .result-valid
|
||||
padding-top: 2.5rem;
|
||||
text-align: center;
|
||||
color: #999;
|
||||
|
||||
.SortableTreeTable
|
||||
.SortableTreeTableHeader,
|
||||
.SortableTreeTableRow
|
||||
.flex-row
|
||||
display: flex;
|
||||
.thtd
|
||||
border: 1px solid #eceeef;
|
||||
flex-grow: 1;
|
||||
// 垂直居中
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: row;
|
||||
margin-right: -1px;
|
||||
.th
|
||||
@extend .thtd;
|
||||
padding: .75rem;
|
||||
font-weight: bold;
|
||||
.td
|
||||
@extend .thtd;
|
||||
padding: .5rem .75rem;
|
||||
margin-bottom: -1px;
|
||||
.th, .td
|
||||
&.operations
|
||||
width: 4.5rem;
|
||||
min-width: 4.5rem;
|
||||
&.name
|
||||
width: 20rem;
|
||||
flex-grow: 3;
|
||||
&.type
|
||||
width: 7rem;
|
||||
&.rule
|
||||
width: 10rem;
|
||||
&.value
|
||||
width: 10rem;
|
||||
&.desc
|
||||
width: 15rem;
|
||||
.th
|
||||
.helper
|
||||
margin-left: .5rem;
|
||||
color: #999;
|
||||
&:hover
|
||||
color: $brand;
|
||||
.td
|
||||
&.operations
|
||||
padding: .5rem .75rem;
|
||||
height: auto;
|
||||
line-height: 1;
|
||||
justify-content: flex-end;
|
||||
a
|
||||
color: #999;
|
||||
margin-right: .5rem;
|
||||
&:last-child
|
||||
margin-right: 0;
|
||||
&:hover
|
||||
color: $brand;
|
||||
&.payload
|
||||
padding: .5rem .75rem;
|
||||
height: auto;
|
||||
line-height: 1.5;
|
||||
&.payload.name
|
||||
@for $i from 0 through 42
|
||||
&.depth-#{$i}
|
||||
padding-left: $i * 1rem + 0.75rem;
|
||||
&.payload.value
|
||||
word-break: break-word;
|
||||
.SortableTreeTable.editable
|
||||
.flex-row
|
||||
.td
|
||||
&.payload
|
||||
padding: 0;
|
||||
&.payload.name
|
||||
@for $i from 0 through 10
|
||||
&.depth-#{$i}
|
||||
padding-left: $i * 1rem;
|
||||
input.editable,
|
||||
select.editable,
|
||||
textarea.editable
|
||||
margin: 0;
|
||||
padding: .5rem .75rem;
|
||||
width: 100%;
|
||||
height: auto;
|
||||
border: none;
|
||||
border-radius: 0;
|
||||
line-height: 1.5;
|
||||
outline: none;
|
||||
box-shadow: none;
|
||||
background-color: transparent;
|
||||
|
||||
.Importer
|
||||
.CodeMirror
|
||||
height: 30rem;
|
||||
textarea.result
|
||||
font-family: Menlo, Monaco, 'Courier New', monospace
|
@ -0,0 +1,53 @@
|
||||
import React from 'react'
|
||||
import { connect, Link } from '../../family'
|
||||
import { Spin } from '../utils'
|
||||
import OwnedRepositoriesCard from './OwnedRepositoriesCard'
|
||||
import JoinedRepositoriesCard from './JoinedRepositoriesCard'
|
||||
import LogsCard from './LogsCard'
|
||||
import './Home.css'
|
||||
import { GoRepo } from 'react-icons/lib/go'
|
||||
|
||||
const Maiden = () => (
|
||||
<div className='Maiden'>
|
||||
<Link to='/repository/joined/create' className=' btn btn-lg btn-success'><GoRepo /> 新建仓库</Link>
|
||||
</div>
|
||||
)
|
||||
|
||||
// 展示组件
|
||||
const Home = ({ auth, owned, joined, logs }) => {
|
||||
if (owned.fetching || joined.fetching || logs.fetching) return <Spin />
|
||||
|
||||
if (!owned.data.length && !joined.data.length) {
|
||||
return (
|
||||
<div className='Home'>
|
||||
<Maiden />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return (
|
||||
<div className='Home'>
|
||||
<div className='row'>
|
||||
<div className='col-12 col-sm-8 col-md-8 col-lg-8'>
|
||||
<LogsCard logs={logs} />
|
||||
</div>
|
||||
<div className='col-12 col-sm-4 col-md-4 col-lg-4'>
|
||||
<OwnedRepositoriesCard repositories={owned} />
|
||||
<JoinedRepositoriesCard repositories={joined} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
// 容器组件
|
||||
const mapStateToProps = (state) => ({
|
||||
auth: state.auth,
|
||||
owned: state.ownedRepositories,
|
||||
joined: state.joinedRepositories,
|
||||
logs: state.logs
|
||||
})
|
||||
const mapDispatchToProps = ({})
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(Home)
|
@ -0,0 +1,59 @@
|
||||
@import "../../assets/variables.sass";
|
||||
|
||||
.Home
|
||||
padding: 2rem;
|
||||
.Maiden
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
.btn
|
||||
font-size: 1.6rem;
|
||||
.card
|
||||
margin-bottom: 2rem;
|
||||
border: 1px solid $border;
|
||||
border-radius: .5rem;
|
||||
overflow: hidden;
|
||||
.card-header
|
||||
border-bottom: 1px solid $border;
|
||||
font-size: 1.6rem;
|
||||
.card-block
|
||||
> p:last-child
|
||||
margin-bottom: 0;
|
||||
a:hover
|
||||
border-bottom: 1px solid $brand;
|
||||
.card:last-child
|
||||
// margin-bottom: 0;
|
||||
.card.Logs
|
||||
.Log
|
||||
padding: 1rem 0;
|
||||
border-bottom: 1px solid #eff3f6;
|
||||
.Log-body
|
||||
float: left;
|
||||
.Log-user
|
||||
// margin-right: .5rem;
|
||||
.Log-avatar
|
||||
margin-right: .75rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
.Log-user-link
|
||||
margin-right: .5rem;
|
||||
.Log-type
|
||||
margin-right: .5rem;
|
||||
.Log-target
|
||||
margin-right: .5rem;
|
||||
.slash
|
||||
color: #999;
|
||||
.Log-footer
|
||||
float: right;
|
||||
margin-left: .5rem;
|
||||
padding: .4rem 0;
|
||||
.Log-fromnow
|
||||
color: #666;
|
||||
|
||||
// padding: 4rem 1rem;
|
||||
// text-align: center;
|
||||
// font-size: 4rem;
|
||||
.Log:last-child
|
||||
border-bottom: 0;
|
@ -0,0 +1,30 @@
|
||||
import React from 'react'
|
||||
import { Link } from '../../family'
|
||||
import { Spin } from '../utils'
|
||||
|
||||
const JoinedRepositoriesCard = ({ repositories }) => (
|
||||
<div className='card'>
|
||||
<div className='card-header'>我加入的仓库</div>
|
||||
{repositories.fetching ? <Spin /> : (
|
||||
<div className='card-block'>
|
||||
{repositories.data.slice(0, 10).map(repository =>
|
||||
<p key={repository.id}><JoinedRepositoryLink repository={repository} /></p>
|
||||
)}
|
||||
{repositories.data.length === 0 ? <span>-</span> : null}
|
||||
{repositories.data.length > 10
|
||||
? <Link to='/repository/joined'>=> 查看全部 {repositories.data.length} 个仓库</Link>
|
||||
: null
|
||||
}
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
const JoinedRepositoryLink = ({ repository }) => (
|
||||
<Link to={`/repository/editor?id=${repository.id}`}>
|
||||
<span>{repository.organization ? repository.organization.name : repository.owner.fullname}</span>
|
||||
<span> / </span>
|
||||
<span>{repository.name}</span>
|
||||
</Link>
|
||||
)
|
||||
|
||||
export default JoinedRepositoriesCard
|
@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
import { Link } from '../../family'
|
||||
import { Spin } from '../utils'
|
||||
|
||||
const OwnedRepositoriesCard = ({ repositories }) => (
|
||||
<div className='card'>
|
||||
<div className='card-header'>我拥有的仓库</div>
|
||||
{repositories.fetching ? <Spin /> : (
|
||||
<div className='card-block'>
|
||||
{repositories.data.slice(0, 10).map(repository =>
|
||||
<p key={repository.id}><OwnedRepositoryLink repository={repository} /></p>
|
||||
)}
|
||||
{repositories.data.length === 0 ? <span>-</span> : null}
|
||||
{repositories.data.length > 10
|
||||
? <Link to='/repository/joined'>=> 查看全部 {repositories.data.length} 个仓库</Link>
|
||||
: null
|
||||
}
|
||||
</div>)
|
||||
}
|
||||
</div>
|
||||
)
|
||||
const OwnedRepositoryLink = ({ repository }) => (
|
||||
<Link to={`/repository/editor?id=${repository.id}`}>
|
||||
<span>{repository.organization ? repository.organization.name + ' / ' : ''}</span>
|
||||
<span>{repository.name}</span>
|
||||
</Link>
|
||||
)
|
||||
|
||||
export default OwnedRepositoriesCard
|
@ -0,0 +1,38 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { contextTypes, childContextTypes, getChildContext, CreateButton, OrganizationsTypeDropdown, SearchGroup, OrganizationListWithSpin, PaginationWithLocation, mapDispatchToProps } from './OrganizationListParts'
|
||||
import './Organization.css'
|
||||
|
||||
// 所有团队
|
||||
class JoinedOrganizationList extends Component {
|
||||
static contextTypes = contextTypes
|
||||
static childContextTypes = childContextTypes
|
||||
getChildContext = getChildContext
|
||||
render () {
|
||||
let { location, match, organizations } = this.props
|
||||
return (
|
||||
<section className='OrganizationListWrapper'>
|
||||
<nav className='toolbar clearfix'>
|
||||
<OrganizationsTypeDropdown url={match.url} />
|
||||
<SearchGroup name={location.params.name} />
|
||||
<CreateButton />
|
||||
</nav>
|
||||
<div className='body'>
|
||||
<OrganizationListWithSpin name={location.params.name} organizations={organizations} />
|
||||
</div>
|
||||
<div className='footer'>
|
||||
<PaginationWithLocation calculated={organizations.pagination} />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
auth: state.auth,
|
||||
organizations: state.organizations
|
||||
})
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JoinedOrganizationList)
|
@ -0,0 +1,47 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { contextTypes, childContextTypes, getChildContext, CreateButton, OrganizationsTypeDropdown, SearchGroup, OrganizationListWithSpin, mapDispatchToProps } from './OrganizationListParts'
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import './Organization.css'
|
||||
|
||||
// 我拥有和加入的团队
|
||||
class JoinedOrganizationList extends Component {
|
||||
static contextTypes = contextTypes
|
||||
static childContextTypes = childContextTypes
|
||||
getChildContext = getChildContext
|
||||
render () {
|
||||
let { location, match, organizations } = this.props
|
||||
return (
|
||||
<section className='OrganizationListWrapper'>
|
||||
<nav className='toolbar clearfix'>
|
||||
<OrganizationsTypeDropdown url={match.url} />
|
||||
<SearchGroup name={location.params.name} />
|
||||
<CreateButton />
|
||||
</nav>
|
||||
<div className='body'>
|
||||
<OrganizationListWithSpin name={location.params.name} organizations={organizations} />
|
||||
</div>
|
||||
<div className='footer'>
|
||||
{/* 没有分页! */}
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
auth: state.auth,
|
||||
joiner: state.auth,
|
||||
organizations: {
|
||||
fetching: state.ownedOrganizations.fetching || state.joinedOrganizations.fetching,
|
||||
data: _.uniqBy([...state.ownedOrganizations.data, ...state.joinedOrganizations.data], 'id')
|
||||
.sort((a, b) => {
|
||||
return moment(b.updatedAt).diff(a.updatedAt)
|
||||
})
|
||||
}
|
||||
})
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JoinedOrganizationList)
|
@ -0,0 +1,56 @@
|
||||
.OrganizationListWrapper
|
||||
padding: 2rem;
|
||||
> .header
|
||||
margin-bottom: 2rem;
|
||||
.title
|
||||
font-size: 2rem;
|
||||
> .toolbar
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid #e1e4e8;
|
||||
select.OrganizationsTypeDropdown
|
||||
margin-right: .5rem;
|
||||
font-size: 1.4rem;
|
||||
button.OrganizationCreateButton
|
||||
padding: .4rem 1rem;
|
||||
font-size: 1.4rem;
|
||||
.form-control
|
||||
margin-bottom: 0;
|
||||
> .body
|
||||
.table
|
||||
> .footer
|
||||
|
||||
.OrganizationList
|
||||
.Organization.card
|
||||
margin-bottom: 1rem;
|
||||
// transition: box-shadow .15s ease-out;
|
||||
// &:hover
|
||||
// box-shadow: 0 0 10px rgba(0, 0, 0, 0.18);
|
||||
.card-block
|
||||
> .header
|
||||
.title
|
||||
margin-bottom: .5rem;
|
||||
font-size: 1.4rem;
|
||||
.toolbar
|
||||
float: right;
|
||||
display: none;
|
||||
margin-left: 1rem;
|
||||
> .body
|
||||
.desc
|
||||
margin-bottom: .5rem;
|
||||
color: #666;
|
||||
.members
|
||||
.avatar
|
||||
margin-right: .2rem;
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
.avatar.owner
|
||||
margin-right: 1rem;
|
||||
.popover
|
||||
white-space: nowrap;
|
||||
.card-block:hover
|
||||
> .header
|
||||
.toolbar
|
||||
display: inline-block
|
||||
|
@ -0,0 +1,35 @@
|
||||
import React, { Component } from 'react'
|
||||
import { PropTypes, connect } from '../../family'
|
||||
import Organization from './Organization'
|
||||
|
||||
// 展示组件
|
||||
class OrganizationList extends Component {
|
||||
// DONE 2.1 补全 propTypes
|
||||
static propTypes = {
|
||||
name: PropTypes.string,
|
||||
organizations: PropTypes.array.isRequired
|
||||
}
|
||||
render () {
|
||||
let { name, organizations } = this.props
|
||||
if (!organizations.length) {
|
||||
return name
|
||||
? <div className='fontsize-14 text-center p40'>没有找到匹配 <strong>{name}</strong> 的团队。</div>
|
||||
: <div className='fontsize-14 text-center p40'>没有数据。</div>
|
||||
}
|
||||
return (
|
||||
<div className='OrganizationList'>
|
||||
{organizations.map(organization =>
|
||||
<Organization key={organization.id} organization={organization} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 容器组件
|
||||
const mapStateToProps = (state) => ({})
|
||||
const mapDispatchToProps = ({})
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(OrganizationList)
|
@ -0,0 +1,49 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { contextTypes, childContextTypes, getChildContext, CreateButton, SearchGroup, mapDispatchToProps, OrganizationRepositoryListWithSpin, PaginationWithLocation } from '../repository/RepositoryListParts'
|
||||
import { Spin } from '../utils'
|
||||
import '../repository/Repository.css'
|
||||
|
||||
// 展示组件
|
||||
class OrganizationRepositoryList extends Component {
|
||||
static contextTypes = contextTypes
|
||||
static propTypes = {}
|
||||
static childContextTypes = childContextTypes
|
||||
getChildContext = getChildContext.bind(this)
|
||||
render () {
|
||||
let { location, auth, organization, repositories } = this.props
|
||||
if (!organization.id) return <Spin />
|
||||
|
||||
let isOwned = organization.owner.id === auth.id
|
||||
let isJoined = organization.members.find(itme => itme.id === auth.id)
|
||||
return (
|
||||
<section className='RepositoryListWrapper'>
|
||||
<div className='header'><span className='title'>{organization.name}</span></div>
|
||||
<nav className='toolbar clearfix'>
|
||||
{isOwned || isJoined
|
||||
? <CreateButton organization={organization} />
|
||||
: null
|
||||
}
|
||||
<SearchGroup name={location.params.name} />
|
||||
</nav>
|
||||
<div className='body'>
|
||||
<OrganizationRepositoryListWithSpin name={location.params.name} repositories={repositories} />
|
||||
</div>
|
||||
<div className='footer'>
|
||||
<PaginationWithLocation calculated={repositories.pagination} />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// 容器组件
|
||||
const mapStateToProps = (state) => ({
|
||||
auth: state.auth,
|
||||
organization: state.organization,
|
||||
repositories: state.repositories // TODO => organizationRepositories
|
||||
})
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(OrganizationRepositoryList)
|
@ -0,0 +1,38 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { contextTypes, childContextTypes, getChildContext, RepositoriesTypeDropdown, SearchGroup, mapDispatchToProps, RepositoryListWithSpin, PaginationWithLocation } from './RepositoryListParts'
|
||||
import './Repository.css'
|
||||
|
||||
// 全部仓库
|
||||
class AllRepositoryList extends Component {
|
||||
static contextTypes = contextTypes
|
||||
static propTypes = {}
|
||||
static childContextTypes = childContextTypes
|
||||
getChildContext = getChildContext.bind(this)
|
||||
render () {
|
||||
let { location, match, repositories } = this.props
|
||||
return (
|
||||
<section className='RepositoryListWrapper'>
|
||||
<nav className='toolbar clearfix'>
|
||||
<RepositoriesTypeDropdown url={match.url} />
|
||||
<SearchGroup name={location.params.name} />
|
||||
</nav>
|
||||
<div className='body'>
|
||||
<RepositoryListWithSpin name={location.params.name} repositories={repositories} />
|
||||
</div>
|
||||
<div className='footer'>
|
||||
<PaginationWithLocation calculated={repositories.pagination} />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
joiner: state.auth,
|
||||
repositories: state.repositories
|
||||
})
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(AllRepositoryList)
|
@ -0,0 +1,47 @@
|
||||
import React, { Component } from 'react'
|
||||
import { connect } from 'react-redux'
|
||||
import { contextTypes, childContextTypes, getChildContext, CreateButton, RepositoriesTypeDropdown, SearchGroup, mapDispatchToProps, RepositoryListWithSpin, PaginationWithLocation } from './RepositoryListParts'
|
||||
import _ from 'lodash'
|
||||
import moment from 'moment'
|
||||
import './Repository.css'
|
||||
|
||||
// 我拥有和加入的仓库
|
||||
class JoinedRepositoryList extends Component {
|
||||
static contextTypes = contextTypes
|
||||
static propTypes = {}
|
||||
static childContextTypes = childContextTypes
|
||||
getChildContext = getChildContext.bind(this)
|
||||
render () {
|
||||
let { location, match, auth, repositories, create } = this.props
|
||||
return (
|
||||
<section className='RepositoryListWrapper'>
|
||||
<nav className='toolbar clearfix'>
|
||||
{(!location.params.user || +location.params.user === auth.id) ? <RepositoriesTypeDropdown url={match.url} /> : null}
|
||||
<SearchGroup name={location.params.name} />
|
||||
{(!location.params.user || +location.params.user === auth.id) ? <CreateButton owner={auth} create={create} callback='/repository/joined' /> : null}
|
||||
</nav>
|
||||
<div className='body'>
|
||||
<RepositoryListWithSpin name={location.params.name} repositories={repositories} />
|
||||
</div>
|
||||
<div className='footer'>
|
||||
<PaginationWithLocation calculated={repositories.pagination} />
|
||||
</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
auth: state.auth,
|
||||
repositories: {
|
||||
fetching: state.ownedRepositories.fetching || state.joinedRepositories.fetching,
|
||||
data: _.uniqBy([...state.ownedRepositories.data, ...state.joinedRepositories.data], 'id')
|
||||
.sort((a, b) => {
|
||||
return moment(b.updatedAt).diff(a.updatedAt)
|
||||
})
|
||||
}
|
||||
})
|
||||
export default connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(JoinedRepositoryList)
|
@ -0,0 +1,74 @@
|
||||
@import "../../assets/variables.sass";
|
||||
|
||||
.RepositoryListWrapper
|
||||
padding: 2rem;
|
||||
> .header
|
||||
margin-bottom: 1rem;
|
||||
.title
|
||||
font-size: 2rem;
|
||||
> .toolbar
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: .5rem;
|
||||
border-bottom: 1px solid #e1e4e8;
|
||||
select.RepositoriesTypeDropdown
|
||||
margin-right: .5rem;
|
||||
margin-bottom: .5rem;
|
||||
font-size: 1.4rem;
|
||||
button.RepositoryCreateButton
|
||||
padding: .4rem 1rem;
|
||||
font-size: 1.4rem;
|
||||
.form-control
|
||||
margin-bottom: .5rem;
|
||||
> .body
|
||||
margin-bottom: 2rem;
|
||||
> .footer
|
||||
|
||||
.RepositoryList
|
||||
.Repository.card
|
||||
margin-bottom: 1rem;
|
||||
// transition: box-shadow .15s ease-out;
|
||||
// &:hover
|
||||
// box-shadow: 0 0 10px rgba(0, 0, 0, 0.18);
|
||||
.card-block
|
||||
position: relative;
|
||||
.name
|
||||
font-size: 1.4rem;
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
.desc
|
||||
margin-top: 1rem;
|
||||
height: 6rem;
|
||||
overflow: hidden;
|
||||
color: #666;
|
||||
.members
|
||||
display: none;
|
||||
height: 2.5rem;
|
||||
.avatar
|
||||
width: 2.5rem;
|
||||
height: 2.5rem;
|
||||
border-radius: 50%;
|
||||
.toolbar
|
||||
// display: none
|
||||
position: absolute;
|
||||
top: 1.2rem;
|
||||
right: 1.25rem;
|
||||
background-color: white;
|
||||
> a, > .fake-link
|
||||
margin: 0 0 0 0.5rem;
|
||||
font-size: 1.4rem;
|
||||
color: #999;
|
||||
&:hover
|
||||
color: $brand;
|
||||
.card-block.card-footer
|
||||
padding-top: 0;
|
||||
background-color: white;
|
||||
border-top: none;
|
||||
color: #666;
|
||||
.ownername
|
||||
float: left;
|
||||
.fromnow
|
||||
float: right;
|
||||
.Repository.card:hover
|
||||
.card-block
|
||||
.toolbar
|
||||
display: block;
|
@ -0,0 +1,30 @@
|
||||
import React, { Component } from 'react'
|
||||
import { PropTypes } from '../../family'
|
||||
import Repository from './Repository'
|
||||
|
||||
class RepositoryList extends Component {
|
||||
static propTypes = {
|
||||
name: PropTypes.string, // 搜索仓库
|
||||
repositories: PropTypes.array.isRequired, // 仓库列表
|
||||
editor: PropTypes.string.isRequired // 仓库编辑器地址
|
||||
}
|
||||
render () {
|
||||
let { name, repositories, editor } = this.props
|
||||
if (!repositories.length) {
|
||||
return name
|
||||
? <div className='fontsize-14 text-center p40'>没有找到匹配 <strong>{name}</strong> 的仓库。</div>
|
||||
: <div className='fontsize-14 text-center p40'>没有数据。</div>
|
||||
}
|
||||
return (
|
||||
<div className='RepositoryList row'>
|
||||
{repositories.map(repository =>
|
||||
<div key={repository.id} className='col-12 col-sm-6 col-md-6 col-lg-4 col-xl-3'>
|
||||
<Repository repository={repository} editor={editor} />
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default RepositoryList
|
@ -0,0 +1,153 @@
|
||||
import React, { Component } from 'react'
|
||||
import { PropTypes, push, replace, URI, StoreStateRouterLocationURI } from '../../family'
|
||||
import { Spin, RModal, Pagination } from '../utils'
|
||||
|
||||
import RepositoryList from './RepositoryList'
|
||||
import RepositoryForm from './RepositoryForm'
|
||||
import { addRepository, updateRepository, deleteRepository } from '../../actions/repository'
|
||||
|
||||
import { GoRepo } from 'react-icons/lib/go'
|
||||
|
||||
export const contextTypes = {
|
||||
store: PropTypes.object
|
||||
}
|
||||
|
||||
export const childContextTypes = {
|
||||
history: PropTypes.object,
|
||||
location: PropTypes.object,
|
||||
match: PropTypes.object,
|
||||
onAddRepository: PropTypes.func,
|
||||
onUpdateRepository: PropTypes.func,
|
||||
onDeleteRepository: PropTypes.func
|
||||
}
|
||||
|
||||
export function getChildContext () {
|
||||
let { history, location, match, onAddRepository, onUpdateRepository, onDeleteRepository } = this.props
|
||||
return { history, location, match, onAddRepository, onUpdateRepository, onDeleteRepository }
|
||||
}
|
||||
|
||||
export const mapDispatchToProps = ({
|
||||
onAddRepository: addRepository,
|
||||
onUpdateRepository: updateRepository,
|
||||
onDeleteRepository: deleteRepository
|
||||
})
|
||||
|
||||
export class CreateButton extends Component {
|
||||
static contextTypes = {
|
||||
store: PropTypes.object.isRequired
|
||||
}
|
||||
static propTypes = {
|
||||
create: PropTypes.bool,
|
||||
callback: PropTypes.string,
|
||||
organization: PropTypes.object
|
||||
}
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = { create: !!props.create }
|
||||
}
|
||||
render () {
|
||||
let { organization } = this.props
|
||||
return (
|
||||
<span className='float-right ml10'>
|
||||
{/* DONE 2.1 √我加入的仓库、X所有仓库 是否应该有 新建仓库 */}
|
||||
<button className='RepositoryCreateButton btn btn-success' onClick={e => this.setState({ create: true })}>
|
||||
<GoRepo /> 新建仓库
|
||||
</button>
|
||||
<RModal when={this.state.create} onClose={e => this.setState({ create: false })} onResolve={this.handleUpdate}>
|
||||
<RepositoryForm title='新建仓库' organization={organization} />
|
||||
</RModal>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
handleUpdate = (e) => {
|
||||
let { callback } = this.props
|
||||
let { store } = this.context
|
||||
if (callback) {
|
||||
store.dispatch(replace(callback))
|
||||
} else {
|
||||
let uri = StoreStateRouterLocationURI(store)
|
||||
store.dispatch(replace(uri.href()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO 2.2 <select> => <Dropdown>
|
||||
export class RepositoriesTypeDropdown extends Component {
|
||||
static contextTypes = {
|
||||
store: PropTypes.object.isRequired
|
||||
}
|
||||
render () {
|
||||
let { url } = this.props
|
||||
return (
|
||||
<select className='RepositoriesTypeDropdown form-control float-left w160 mr12' value={url} onChange={e => this.handlePush(e.target.value)} size='1'>
|
||||
<option value='/repository/joined'>我拥有和加入的仓库</option>
|
||||
<option value='/repository/all'>所有仓库</option>
|
||||
</select>
|
||||
)
|
||||
}
|
||||
handlePush = (url) => {
|
||||
let { store } = this.context
|
||||
store.dispatch(push(url))
|
||||
// let uri = StoreStateRouterLocationURI(store)
|
||||
// store.dispatch(replace(uri.href()))
|
||||
}
|
||||
}
|
||||
|
||||
export class SearchGroup extends Component {
|
||||
static contextTypes = {
|
||||
store: PropTypes.object
|
||||
}
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = { name: props.name || '' }
|
||||
}
|
||||
render () {
|
||||
// <div className='input-group float-right w280'>
|
||||
// <input type='text' value={this.state.name} className='form-control' placeholder='仓库名称或 ID' autoComplete='off'
|
||||
// onChange={e => this.setState({ name: e.target.value.trim() })}
|
||||
// onKeyUp={e => e.which === 13 && this.handleSearch()} />
|
||||
// <span className='btn input-group-addon' onClick={this.handleSearch}><span className=''></span></span>
|
||||
// </div>
|
||||
return (
|
||||
<input type='text' value={this.state.name} className='form-control float-left w280' placeholder='搜索仓库:输入名称或 ID' autoComplete='off'
|
||||
onChange={e => this.setState({ name: e.target.value.trim() })}
|
||||
onKeyUp={e => e.which === 13 && this.handleSearch()}
|
||||
ref={$input => { this.$input = $input }} />
|
||||
)
|
||||
}
|
||||
componentDidMount () {
|
||||
if (this.state.name) {
|
||||
this.$input.focus()
|
||||
}
|
||||
}
|
||||
handleSearch = () => {
|
||||
let { store } = this.context
|
||||
let { pathname, hash, search } = store.getState().router.location
|
||||
let uri = URI(pathname + hash + search).removeSearch('cursor')
|
||||
this.state.name ? uri.setSearch('name', this.state.name) : uri.removeSearch('name')
|
||||
store.dispatch(push(uri.href()))
|
||||
}
|
||||
}
|
||||
|
||||
export const RepositoryListWithSpin = ({ name, repositories }) => (
|
||||
repositories.fetching
|
||||
? <Spin />
|
||||
: <RepositoryList name={name} repositories={repositories.data} editor='/repository/editor' />
|
||||
)
|
||||
|
||||
export const OrganizationRepositoryListWithSpin = ({ name, repositories }) => (
|
||||
repositories.fetching
|
||||
? <Spin />
|
||||
: <RepositoryList name={name} repositories={repositories.data} editor='/organization/repository/editor' />
|
||||
)
|
||||
|
||||
export class PaginationWithLocation extends Component {
|
||||
static contextTypes = {
|
||||
location: PropTypes.object
|
||||
}
|
||||
render () {
|
||||
let { calculated } = this.props
|
||||
let { location } = this.context
|
||||
return <Pagination location={location} calculated={calculated} />
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
.Status
|
||||
padding: 2rem;
|
||||
> .header
|
||||
margin-bottom: 3rem;
|
||||
> .title
|
||||
font-size: 2rem;
|
||||
.body
|
||||
text-align: center;
|
||||
.card
|
||||
margin-bottom: 1rem;
|
||||
border-radius: .4rem;
|
||||
.name
|
||||
font-size: 1rem
|
||||
.value
|
||||
font-size: 5rem;
|
||||
.unit
|
||||
margin-left: 5px;
|
||||
font-size: 1rem;
|
||||
color: #ccc;
|
||||
.chart
|
||||
> .header > .title
|
||||
font-size: 2rem;
|
||||
margin-bottom: 1rem;
|
||||
.footer
|
@ -0,0 +1,53 @@
|
||||
@import "../../assets/variables.sass";
|
||||
|
||||
.Tester
|
||||
.header
|
||||
padding: 2rem 2rem 1rem 2rem;
|
||||
border-bottom: 1px solid #e1e4e8;
|
||||
background-color: #fafbfc;
|
||||
.card-mods, .card-itfs
|
||||
display: flex;
|
||||
margin-bottom: 1rem;
|
||||
> .card-title
|
||||
margin-bottom: 0;
|
||||
width: 5rem;
|
||||
min-width: 5rem;
|
||||
font-size: 1.4rem;
|
||||
> ul
|
||||
margin-bottom: 0;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
> li
|
||||
float: left;
|
||||
margin-right: 1rem;
|
||||
padding: 2px 0;
|
||||
width: 10rem;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
a
|
||||
color: #666;
|
||||
> li.active
|
||||
a
|
||||
color: $brand;
|
||||
.body
|
||||
padding: 2rem;
|
||||
.card-props
|
||||
margin-bottom: 2rem;
|
||||
ul.fields
|
||||
list-style: none;
|
||||
padding-left: 0;
|
||||
> li.filed
|
||||
float: left;
|
||||
margin-right: 1rem;
|
||||
width: 10rem;
|
||||
.label
|
||||
margin-bottom: .5rem;
|
||||
padding-left: .5rem;
|
||||
font-weight: bold;
|
||||
.card-result
|
||||
margin-top: 1rem;
|
||||
.card-title
|
||||
font-size: 1.2rem;
|
||||
margin-bottom: 1rem;
|
||||
ul.fields
|
@ -0,0 +1,75 @@
|
||||
import React, { Component } from 'react'
|
||||
import PropTypes from 'prop-types'
|
||||
import './Dialog.css'
|
||||
|
||||
class Dialog extends Component {
|
||||
static childContextTypes = {
|
||||
dialog: PropTypes.instanceOf(Dialog)
|
||||
}
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = { visible: true }
|
||||
}
|
||||
getChildContext () {
|
||||
return {
|
||||
dialog: this
|
||||
}
|
||||
}
|
||||
open = () => {
|
||||
this.setState({ visible: true }, () => {
|
||||
this.rePosition()
|
||||
})
|
||||
}
|
||||
rePosition = () => {
|
||||
let left = (document.documentElement.clientWidth - this.related.clientWidth) / 2
|
||||
let top = (document.documentElement.clientHeight - this.related.clientHeight) / 2
|
||||
this.related.style.left = left + 'px'
|
||||
this.related.style.top = top + 'px'
|
||||
}
|
||||
handleEsc = (e) => {
|
||||
if (e.keyCode === 27) {
|
||||
this.close()
|
||||
}
|
||||
}
|
||||
close = () => {
|
||||
this.setState({ visible: false }, () => {
|
||||
this.props.onClose()
|
||||
})
|
||||
}
|
||||
componentDidMount () {
|
||||
this.open()
|
||||
document.body.classList.add('modal-open')
|
||||
document.body.addEventListener('keyup', this.handleEsc)
|
||||
window.addEventListener('resize', this.rePosition)
|
||||
}
|
||||
componentWillUnmount () {
|
||||
document.body.classList.remove('modal-open')
|
||||
document.body.removeEventListener('keyup', this.handleEsc)
|
||||
window.removeEventListener('resize', this.rePosition)
|
||||
}
|
||||
render () {
|
||||
if (!this.state.visible) return null
|
||||
return (
|
||||
<div className='dialog' ref={related => { this.related = related }}>
|
||||
<button type='button' className='dialog-close' onClick={this.close}>
|
||||
<span className='rapfont'></span>
|
||||
</button>
|
||||
<div className='dialog-content'>
|
||||
{this.props.content || this.props.children}
|
||||
{/*
|
||||
<div className="dialog-header">
|
||||
<h4 className="dialog-title">Title</h4>
|
||||
</div>
|
||||
<div className="dialog-body">Body</div>
|
||||
<div className="dialog-footer">
|
||||
<button type="button" className="btn btn-default">Close</button>
|
||||
<button type="button" className="btn btn-success">Save</button>
|
||||
</div>
|
||||
*/}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default Dialog
|
@ -0,0 +1,55 @@
|
||||
@import "../../assets/variables.sass";
|
||||
|
||||
.dialog
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: white;
|
||||
border: none;
|
||||
border-radius: .5rem;
|
||||
box-shadow: none;
|
||||
z-index: 1040;
|
||||
|
||||
.dialog-close
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 1rem;
|
||||
border: none;
|
||||
background-color: transparent;
|
||||
opacity: .2;
|
||||
|
||||
.rapfont
|
||||
font-size: 3rem;
|
||||
|
||||
&:hover, &:focus
|
||||
color: #000;
|
||||
text-decoration: none;
|
||||
filter: alpha(opacity=50);
|
||||
opacity: .5;
|
||||
|
||||
.dialog-content
|
||||
.dialog-header
|
||||
border-bottom: 1px solid $border;
|
||||
padding: 1.5rem 4rem 1.5rem 3rem;
|
||||
text-align: left;
|
||||
.dialog-title
|
||||
margin: 0;
|
||||
font-size: 1.8rem;
|
||||
.dialog-body
|
||||
padding: 1.5rem 3rem;
|
||||
.dialog-footer
|
||||
text-align: left;
|
||||
border-top: 1px solid $border;
|
||||
padding: 1.5rem 3rem;
|
||||
|
||||
.dialog-backdrop
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background-color: #000;
|
||||
filter: alpha(opacity=50);
|
||||
opacity: .5;
|
||||
// display: none;
|
||||
z-index: 1039;
|
@ -0,0 +1,25 @@
|
||||
import React, { Component } from 'react'
|
||||
|
||||
// TODO 2.x 如何写高阶 Form 组件
|
||||
class Form extends Component {
|
||||
render () {
|
||||
let { children } = this.props
|
||||
return (
|
||||
<form onSubmit={this.handleSubmit}>
|
||||
{children}
|
||||
</form>
|
||||
)
|
||||
}
|
||||
handleSubmit = (event) => {
|
||||
event.preventDefault()
|
||||
let { onSubmit, onResolved, onRejected } = this.props
|
||||
var values = {};
|
||||
([]).slice.call(event.target.elements, 0).forEach(el => {
|
||||
if (el.name) values[el.name] = el.value
|
||||
})
|
||||
onSubmit(values)
|
||||
onResolved()
|
||||
onRejected()
|
||||
}
|
||||
}
|
||||
export default Form
|
@ -0,0 +1,82 @@
|
||||
import React, { Component } from 'react'
|
||||
import _ from 'lodash'
|
||||
import AccountService from '../../relatives/services/Account'
|
||||
import './TagsInput.css'
|
||||
import { GoX } from 'react-icons/lib/go'
|
||||
|
||||
// TODO 2.3 input 宽度自适应
|
||||
// TODO 2.3 回退删除 回退选中
|
||||
class MembersInput extends Component {
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = {
|
||||
seed: '',
|
||||
value: props.value || [], // [{ id, fullname, email }]
|
||||
options: [], // [{ id, fullname, email }]
|
||||
limit: props.limit
|
||||
}
|
||||
}
|
||||
componentWillReceiveProps (nextProps) {
|
||||
this.setState({
|
||||
value: nextProps.value || []
|
||||
})
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<div className='TagsInput clearfix' onClick={e => this.$seed && this.$seed.focus()}>
|
||||
{this.state.value.map(item =>
|
||||
<span key={item.id} className='tag'>
|
||||
<span className='label'>{item.fullname}</span>
|
||||
<span className='remove' onClick={e => this.handleRemove(item)}><GoX /></span>
|
||||
</span>
|
||||
)}
|
||||
{(!this.state.limit || this.state.value.length < this.state.limit) &&
|
||||
<div className='dropdown'>
|
||||
<input className='dropdown-input' value={this.state.seed} placeholder='名字检索' autoComplete='off'
|
||||
onChange={e => this.handleSeed(e.target.value)}
|
||||
ref={$seed => { this.$seed = $seed }} />
|
||||
{this.state.options.length ? (
|
||||
<div className='dropdown-menu' ref={$optons => { this.$optons = $optons }}>
|
||||
{this.state.options.map(item =>
|
||||
<a key={item.id} href='' className='dropdown-item'
|
||||
onClick={e => this.handleSelect(e, item)}>{item.fullname}</a>
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
handleSeed = async (seed) => {
|
||||
this.setState({ seed: seed })
|
||||
if (!seed) {
|
||||
this.setState({ options: [] })
|
||||
return
|
||||
}
|
||||
let users = await AccountService.fetchUserList({ name: seed })
|
||||
let options = _.differenceWith(users.data, this.state.value, _.isEqual)
|
||||
this.setState({ options })
|
||||
}
|
||||
handleSelect = (e, selected) => {
|
||||
e.preventDefault()
|
||||
let nextState = { seed: '', value: [...this.state.value, selected] }
|
||||
this.setState(nextState, this.handleChange)
|
||||
}
|
||||
// √remove vs delete
|
||||
handleRemove = (removed) => {
|
||||
let nextState = { value: this.state.value.filter(item => item !== removed) }
|
||||
this.setState(nextState, this.handleChange)
|
||||
}
|
||||
// √change vs update
|
||||
handleChange = () => {
|
||||
let { onChange } = this.props
|
||||
onChange(this.state.value)
|
||||
this.$seed && this.$seed.focus()
|
||||
this.setState({
|
||||
options: []
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default MembersInput
|
@ -0,0 +1,51 @@
|
||||
@import "../../assets/variables.sass";
|
||||
|
||||
.Modal
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
max-height: 100%;
|
||||
overflow: scroll;
|
||||
border: none;
|
||||
border-radius: .5rem;
|
||||
box-shadow: none;
|
||||
background-color: white;
|
||||
z-index: 1040;
|
||||
|
||||
button.close
|
||||
position: absolute;
|
||||
right: 1rem;
|
||||
top: 1rem;
|
||||
border: none;
|
||||
color: #000;
|
||||
background-color: transparent;
|
||||
opacity: .2;
|
||||
&:hover, &:focus
|
||||
opacity: .5;
|
||||
text-decoration: none;
|
||||
.rapfont
|
||||
font-size: 3rem;
|
||||
.modal-header
|
||||
border-bottom: 1px solid $border;
|
||||
padding: 1.5rem 4rem 1.5rem 3rem;
|
||||
text-align: left;
|
||||
.modal-title
|
||||
margin: 0;
|
||||
font-size: 1.8rem;
|
||||
.modal-body
|
||||
padding: 1.5rem 3rem;
|
||||
.modal-footer
|
||||
text-align: left;
|
||||
border-top: 1px solid $border;
|
||||
padding: 1.5rem 3rem;
|
||||
|
||||
.modal-backdrop
|
||||
position: fixed;
|
||||
top: 0;
|
||||
right: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
background-color: #000;
|
||||
filter: alpha(opacity=50);
|
||||
opacity: .5;
|
||||
z-index: 1039;
|
@ -0,0 +1,54 @@
|
||||
import React, { Component } from 'react'
|
||||
import { PropTypes, connect } from '../../family'
|
||||
import Modal from './Modal'
|
||||
import './Utils.css'
|
||||
|
||||
class ModalContent extends Component {
|
||||
static contextTypes = {
|
||||
store: PropTypes.object.isRequired
|
||||
}
|
||||
static propTypes = {
|
||||
auth: PropTypes.object.isRequired
|
||||
}
|
||||
render () {
|
||||
return (
|
||||
<div style={{ width: '400px', height: '400px' }}>
|
||||
{JSON.stringify(this.props.auth)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
const mapStateToProps = (state) => ({
|
||||
auth: state.auth
|
||||
})
|
||||
const mapDispatchToProps = ({})
|
||||
|
||||
let ModalContentContainer = connect(
|
||||
mapStateToProps,
|
||||
mapDispatchToProps
|
||||
)(ModalContent)
|
||||
|
||||
class ModalExample extends Component {
|
||||
static contextTypes = {}
|
||||
constructor (props) {
|
||||
super(props)
|
||||
this.state = { visible: false }
|
||||
}
|
||||
render () {
|
||||
let visible = this.state.visible
|
||||
return (
|
||||
<div>
|
||||
<button onClick={e => this.setState({ visible: !visible })}>Trigger</button>
|
||||
{visible &&
|
||||
<Modal onClose={e => this.setState({ visible: false })}>
|
||||
<ModalContentContainer />
|
||||
<ModalContentContainer />
|
||||
<ModalContentContainer />
|
||||
</Modal>
|
||||
}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default ModalExample
|
@ -0,0 +1,7 @@
|
||||
import React from 'react'
|
||||
|
||||
export default ({ location }) => (
|
||||
<div className='p100 fontsize-16 text-center'>
|
||||
<span>No match for <code>{location.pathname}</code></span>
|
||||
</div>
|
||||
)
|
@ -0,0 +1,33 @@
|
||||
@import "../../assets/variables.sass";
|
||||
|
||||
.Pagination
|
||||
.summary
|
||||
float: left;
|
||||
padding: 0.5rem 0;
|
||||
.page-list
|
||||
float: right;
|
||||
display: flex;
|
||||
margin-bottom: 0;
|
||||
padding-left: 0;
|
||||
list-style: none;
|
||||
.page-item
|
||||
margin: 0 .2rem;
|
||||
.page-link
|
||||
display: inline-block;
|
||||
padding: 0.5rem 1rem;
|
||||
border-radius: 0.4rem;
|
||||
&:hover
|
||||
color: white;
|
||||
background-color: $brand-hover;
|
||||
.page-item-disabled
|
||||
@extend .page-item;
|
||||
.page-link
|
||||
color: #ccc;
|
||||
pointer-events: none;
|
||||
cursor: not-allowed;
|
||||
.page-item-active
|
||||
@extend .page-item;
|
||||
.page-link
|
||||
color: white;
|
||||
background-color: $brand;
|
||||
|
@ -0,0 +1,40 @@
|
||||
@import "../../assets/variables.sass";
|
||||
|
||||
.Popover
|
||||
position: relative;
|
||||
|
||||
.popover
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
z-index: 1060;
|
||||
max-width: 30rem;
|
||||
display: block;
|
||||
padding: 0;
|
||||
border: 1px solid $border;
|
||||
border-radius: 4px;
|
||||
box-shadow: 0 6px 8px rgba(51, 51, 51, 0.08);
|
||||
font-family: $font-family;
|
||||
background-color: white;
|
||||
|
||||
&.top { margin-top: -10px; }
|
||||
&.right { margin-left: 10px; }
|
||||
&.bottom { margin-top: 10px; }
|
||||
&.left { margin-left: -10px; }
|
||||
|
||||
.arrow
|
||||
display: none;
|
||||
|
||||
.popover-title
|
||||
margin: 0;
|
||||
padding: 0.5rem 1rem; // 8px 14px
|
||||
border-bottom: 1px solid #ebebeb;
|
||||
border-radius: .4rem .4px 0 0;
|
||||
font-size: 14px;
|
||||
background-color: #fafafa;
|
||||
|
||||
.popover-content
|
||||
margin: 0;
|
||||
padding: 0.5rem 1rem; // 8px 14px
|
||||
font-size: 12px;
|
||||
line-height: 22px;
|
@ -0,0 +1,10 @@
|
||||
import React from 'react'
|
||||
import Popover from './Popover'
|
||||
|
||||
export default () => (
|
||||
<div>
|
||||
{['top', 'right', 'bottom', 'left'].map(placement =>
|
||||
<Popover key={placement} placement={placement} title={placement} content='Envy is the ulcer of the soul.' className='btn btn-default mr10'>Popover on {placement}</Popover>
|
||||
)}
|
||||
</div>
|
||||
)
|
@ -0,0 +1,29 @@
|
||||
import React, { Component } from 'react'
|
||||
import { render } from 'react-dom'
|
||||
|
||||
// TODO 2.x http://stackoverflow.com/questions/28802179/how-to-create-a-react-modalwhich-is-append-to-body-with-transitions
|
||||
|
||||
let PORTAL_ID = 1
|
||||
class Portal extends Component {
|
||||
render () { return null }
|
||||
$portal: null
|
||||
componentDidMount () {
|
||||
let id = 'Portal-' + PORTAL_ID++
|
||||
let $portal = document.getElementById(id)
|
||||
if (!$portal) {
|
||||
$portal = document.createElement('div')
|
||||
$portal.id = id
|
||||
document.body.appendChild($portal)
|
||||
}
|
||||
this.$portal = $portal
|
||||
this.componentDidUpdate()
|
||||
}
|
||||
componentWillUnmount () {
|
||||
document.body.removeChild(this.$portal)
|
||||
}
|
||||
componentDidUpdate () {
|
||||
render(<div {...this.props}>{this.props.children}</div>, this.$portal)
|
||||
}
|
||||
}
|
||||
|
||||
export default Portal
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue