Compare commits
249 Commits
tenraku
...
feature-xj
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2b804d7608 | ||
|
|
4563274789 | ||
|
|
3b9f08b43d | ||
|
|
4c8cc1def9 | ||
|
|
7284f982a3 | ||
|
|
ed27a18d25 | ||
|
|
40074fb162 | ||
|
|
96ec2a059e | ||
|
|
d833ebb086 | ||
|
|
f26ef1dd42 | ||
|
|
7ac722081e | ||
|
|
fa120d2ce9 | ||
|
|
1f0b05ee13 | ||
|
|
c2c6dee8c5 | ||
|
|
43ad0f5dd8 | ||
|
|
a3375c4526 | ||
|
|
1028327a37 | ||
|
|
f5b5607297 | ||
| dd814993f1 | |||
| 9dce750ee5 | |||
| 2ffa1d9438 | |||
|
|
9d853944cb | ||
|
|
5a670e7ef9 | ||
|
|
b8e76e0dc1 | ||
|
|
3a29aad32e | ||
|
|
b7dd258c0a | ||
|
|
2d7c9a5c3f | ||
|
|
9ddd3783f6 | ||
|
|
886969e941 | ||
|
|
ae67ec8751 | ||
|
|
138ede6191 | ||
|
|
f30ffaa137 | ||
|
|
30f44ca923 | ||
|
|
58bf916810 | ||
|
|
843db5f10c | ||
|
|
1e4cb27998 | ||
|
|
df408ff2a8 | ||
|
|
32ffee0c93 | ||
|
|
7ec2b6df28 | ||
|
|
662af6a226 | ||
|
|
4eb66684da | ||
|
|
20ca47c004 | ||
|
|
90bcfc30b9 | ||
|
|
db8d942eaf | ||
|
|
c2793698c5 | ||
|
|
1654387fe5 | ||
|
|
9dd2ffd549 | ||
|
|
04200193a8 | ||
|
|
91d52cb6e2 | ||
|
|
fc510c6aec | ||
|
|
d416b5e5cb | ||
|
|
cc8e5389d4 | ||
|
|
8859a6c57a | ||
|
|
9f7b6f0c83 | ||
|
|
b3c65fb4b5 | ||
|
|
c907fd8473 | ||
|
|
ad827c1dc8 | ||
|
|
c7eb8171ef | ||
|
|
a7783987a8 | ||
|
|
3925a0a721 | ||
|
|
814e0b1842 | ||
|
|
5fc03c6fe0 | ||
|
|
60359ed9bd | ||
|
|
e492659dbf | ||
|
|
c482b9ff5f | ||
|
|
fcbbecea75 | ||
|
|
329b28c459 | ||
|
|
55e69380aa | ||
|
|
af22f6e603 | ||
|
|
1e3b2d6392 | ||
|
|
d1634b6e81 | ||
|
|
ccb64a020b | ||
|
|
c3f6de6733 | ||
|
|
82ef3ebde0 | ||
|
|
1cbb519c92 | ||
|
|
b5fa5cdf57 | ||
|
|
acf8f0489d | ||
|
|
2f11323193 | ||
|
|
9eb87fe3f3 | ||
|
|
fe311a2be4 | ||
|
|
8b9f83ab25 | ||
|
|
22a8bf99ca | ||
|
|
f699e25090 | ||
|
|
cca7a1ba22 | ||
|
|
53e5a449d4 | ||
|
|
c723b500b3 | ||
|
|
4fcbf25233 | ||
|
|
b3bc646147 | ||
|
|
392b7caa7f | ||
|
|
d9b3f57191 | ||
|
|
5889874720 | ||
|
|
7e6143cac7 | ||
|
|
4a4a9d72e6 | ||
|
|
914b0d85df | ||
|
|
ad96c923b2 | ||
|
|
43994ca213 | ||
|
|
29cfed37f4 | ||
|
|
119091eaee | ||
|
|
d7e48483e9 | ||
|
|
a6d49c3f96 | ||
|
|
a2f57f06cf | ||
|
|
f626f5722b | ||
|
|
5cc4ece713 | ||
|
|
b6db5f274e | ||
|
|
936ef54072 | ||
|
|
dfaa77f2b9 | ||
|
|
26d0805dd9 | ||
|
|
850383d1d2 | ||
|
|
a4e9d73f3e | ||
|
|
35df63664e | ||
|
|
6cd4fb9327 | ||
|
|
f1b0b0a820 | ||
|
|
70aa9ef914 | ||
|
|
bf4ddba490 | ||
|
|
48f2c4a2d1 | ||
|
|
24fca834e0 | ||
|
|
a81f5e8c7f | ||
|
|
b6a68198f5 | ||
|
|
7bfba06317 | ||
|
|
1dd13487bd | ||
|
|
df2dbe7b8b | ||
|
|
48d2d9c473 | ||
|
|
748ccb8029 | ||
|
|
c50e84a01f | ||
|
|
1262f6040b | ||
|
|
838388fe08 | ||
|
|
770e31accd | ||
|
|
6023237db9 | ||
|
|
ce7973a635 | ||
|
|
d2b1e03a5f | ||
|
|
96722d9c2f | ||
|
|
9186cfb3d0 | ||
|
|
5079dffc25 | ||
|
|
f0b76057bb | ||
|
|
a96477be9a | ||
|
|
8b63bfc784 | ||
|
|
9cbd07db37 | ||
|
|
0a3182431f | ||
|
|
0bbd98ad78 | ||
|
|
95bc3575d2 | ||
|
|
f0f282afe0 | ||
|
|
e9eafdaf1a | ||
|
|
92864eb6ad | ||
|
|
cc4276b727 | ||
|
|
b9a7dd99da | ||
|
|
d4ade4c167 | ||
|
|
c6a577b5ec | ||
|
|
6fff3ec006 | ||
|
|
64e72a66d5 | ||
|
|
af5f27c8c5 | ||
|
|
5823c989c2 | ||
|
|
ee362a6a93 | ||
|
|
6ba1e0d958 | ||
|
|
fde66aa480 | ||
|
|
79a8598468 | ||
|
|
f70c27d814 | ||
|
|
432e52d322 | ||
|
|
f3893c2500 | ||
|
|
e726843189 | ||
|
|
183abeba41 | ||
|
|
70d2513cd7 | ||
|
|
0fda3d143c | ||
|
|
a85a3683f2 | ||
|
|
14287b6948 | ||
|
|
0443257f86 | ||
|
|
18b97c249a | ||
|
|
4ac4c9e9f4 | ||
|
|
24a70aed2e | ||
|
|
79e38ba6dd | ||
|
|
303a3ffc23 | ||
|
|
af86edd3e2 | ||
|
|
c87cff4181 | ||
|
|
05db5a0522 | ||
|
|
bac7020c15 | ||
|
|
c1d33e3ff0 | ||
| 832d46d360 | |||
|
|
c8f9cbda9a | ||
|
|
c1c265c73e | ||
| e4800d2937 | |||
| 550e59b4db | |||
|
|
26a685b872 | ||
|
|
8514adf15e | ||
|
|
5f2059fd6a | ||
| 504a76b4ac | |||
|
|
80694ee49c | ||
|
|
493b9ca0e9 | ||
|
|
55bbf50656 | ||
| 4b27504b99 | |||
|
|
1d248bde43 | ||
|
|
5cad10575f | ||
|
|
3b56c78bf1 | ||
|
|
6ab668f86a | ||
|
|
7b1daaab33 | ||
|
|
ef47912c37 | ||
|
|
140c48bcb7 | ||
| ba0b96146e | |||
| 53aa5dff88 | |||
| e52b02ec7f | |||
| 47dbaaf87d | |||
| 478c751ea7 | |||
| 4ee72a8a75 | |||
|
|
e2db112080 | ||
|
|
3c0d572a0e | ||
|
|
c225ddd39d | ||
| 0e9b0ea693 | |||
| 36f225a5b6 | |||
| 9496128e02 | |||
| 612962cc83 | |||
| 52514b7197 | |||
|
|
234e55bc01 | ||
|
|
f4a1bc3e58 | ||
|
|
192174b2ca | ||
|
|
544370688e | ||
|
|
6a6e772e32 | ||
|
|
f4500a09bc | ||
|
|
e7c3d3c8ad | ||
|
|
91bd72f7e0 | ||
|
|
2e69dc4dcf | ||
|
|
535049a188 | ||
|
|
5bde55e5fc | ||
|
|
c1cad3d7a9 | ||
|
|
c378bfe20c | ||
|
|
0e0d028c24 | ||
|
|
3b6eed32ec | ||
|
|
f62a7c3389 | ||
|
|
a161d8e2c8 | ||
|
|
b97888fca9 | ||
|
|
371ec3a133 | ||
|
|
711b9afaea | ||
|
|
00227ca713 | ||
|
|
842edd6f1f | ||
|
|
cd0c3197fa | ||
|
|
7f35a91765 | ||
| c44b42f498 | |||
| cff6ee5478 | |||
|
|
40b21604f9 | ||
|
|
98fcd2eb47 | ||
|
|
c3b560dbc9 | ||
| 53aadfcaaa | |||
|
|
7fb3d08ccb | ||
|
|
cf4209333d | ||
|
|
61ac281134 | ||
|
|
22e9094d4c | ||
|
|
a13721f63e | ||
|
|
05b9a0ce1b | ||
|
|
b25c17ab53 | ||
|
|
64d2cadd82 | ||
|
|
371e2ee073 | ||
|
|
a7078b54c5 |
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,3 +1,7 @@
|
|||||||
.vscode
|
.vscode
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
docker-stack.yml
|
docker-stack.yml
|
||||||
|
backend/pyvenv.cfg
|
||||||
|
backend/Include/
|
||||||
|
backend/Scripts/
|
||||||
|
|
||||||
|
|||||||
@@ -1,502 +0,0 @@
|
|||||||
<#
|
|
||||||
.Synopsis
|
|
||||||
Activate a Python virtual environment for the current PowerShell session.
|
|
||||||
|
|
||||||
.Description
|
|
||||||
Pushes the python executable for a virtual environment to the front of the
|
|
||||||
$Env:PATH environment variable and sets the prompt to signify that you are
|
|
||||||
in a Python virtual environment. Makes use of the command line switches as
|
|
||||||
well as the `pyvenv.cfg` file values present in the virtual environment.
|
|
||||||
|
|
||||||
.Parameter VenvDir
|
|
||||||
Path to the directory that contains the virtual environment to activate. The
|
|
||||||
default value for this is the parent of the directory that the Activate.ps1
|
|
||||||
script is located within.
|
|
||||||
|
|
||||||
.Parameter Prompt
|
|
||||||
The prompt prefix to display when this virtual environment is activated. By
|
|
||||||
default, this prompt is the name of the virtual environment folder (VenvDir)
|
|
||||||
surrounded by parentheses and followed by a single space (ie. '(.venv) ').
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1
|
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script.
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1 -Verbose
|
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
|
||||||
and shows extra information about the activation as it executes.
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1 -VenvDir C:\Users\MyUser\Common\.venv
|
|
||||||
Activates the Python virtual environment located in the specified location.
|
|
||||||
|
|
||||||
.Example
|
|
||||||
Activate.ps1 -Prompt "MyPython"
|
|
||||||
Activates the Python virtual environment that contains the Activate.ps1 script,
|
|
||||||
and prefixes the current prompt with the specified string (surrounded in
|
|
||||||
parentheses) while the virtual environment is active.
|
|
||||||
|
|
||||||
.Notes
|
|
||||||
On Windows, it may be required to enable this Activate.ps1 script by setting the
|
|
||||||
execution policy for the user. You can do this by issuing the following PowerShell
|
|
||||||
command:
|
|
||||||
|
|
||||||
PS C:\> Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
|
||||||
|
|
||||||
For more information on Execution Policies:
|
|
||||||
https://go.microsoft.com/fwlink/?LinkID=135170
|
|
||||||
|
|
||||||
#>
|
|
||||||
Param(
|
|
||||||
[Parameter(Mandatory = $false)]
|
|
||||||
[String]
|
|
||||||
$VenvDir,
|
|
||||||
[Parameter(Mandatory = $false)]
|
|
||||||
[String]
|
|
||||||
$Prompt
|
|
||||||
)
|
|
||||||
|
|
||||||
<# Function declarations --------------------------------------------------- #>
|
|
||||||
|
|
||||||
<#
|
|
||||||
.Synopsis
|
|
||||||
Remove all shell session elements added by the Activate script, including the
|
|
||||||
addition of the virtual environment's Python executable from the beginning of
|
|
||||||
the PATH variable.
|
|
||||||
|
|
||||||
.Parameter NonDestructive
|
|
||||||
If present, do not remove this function from the global namespace for the
|
|
||||||
session.
|
|
||||||
|
|
||||||
#>
|
|
||||||
function global:deactivate ([switch]$NonDestructive) {
|
|
||||||
# Revert to original values
|
|
||||||
|
|
||||||
# The prior prompt:
|
|
||||||
if (Test-Path -Path Function:_OLD_VIRTUAL_PROMPT) {
|
|
||||||
Copy-Item -Path Function:_OLD_VIRTUAL_PROMPT -Destination Function:prompt
|
|
||||||
Remove-Item -Path Function:_OLD_VIRTUAL_PROMPT
|
|
||||||
}
|
|
||||||
|
|
||||||
# The prior PYTHONHOME:
|
|
||||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PYTHONHOME) {
|
|
||||||
Copy-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME -Destination Env:PYTHONHOME
|
|
||||||
Remove-Item -Path Env:_OLD_VIRTUAL_PYTHONHOME
|
|
||||||
}
|
|
||||||
|
|
||||||
# The prior PATH:
|
|
||||||
if (Test-Path -Path Env:_OLD_VIRTUAL_PATH) {
|
|
||||||
Copy-Item -Path Env:_OLD_VIRTUAL_PATH -Destination Env:PATH
|
|
||||||
Remove-Item -Path Env:_OLD_VIRTUAL_PATH
|
|
||||||
}
|
|
||||||
|
|
||||||
# Just remove the VIRTUAL_ENV altogether:
|
|
||||||
if (Test-Path -Path Env:VIRTUAL_ENV) {
|
|
||||||
Remove-Item -Path env:VIRTUAL_ENV
|
|
||||||
}
|
|
||||||
|
|
||||||
# Just remove VIRTUAL_ENV_PROMPT altogether.
|
|
||||||
if (Test-Path -Path Env:VIRTUAL_ENV_PROMPT) {
|
|
||||||
Remove-Item -Path env:VIRTUAL_ENV_PROMPT
|
|
||||||
}
|
|
||||||
|
|
||||||
# Just remove the _PYTHON_VENV_PROMPT_PREFIX altogether:
|
|
||||||
if (Get-Variable -Name "_PYTHON_VENV_PROMPT_PREFIX" -ErrorAction SilentlyContinue) {
|
|
||||||
Remove-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Scope Global -Force
|
|
||||||
}
|
|
||||||
|
|
||||||
# Leave deactivate function in the global namespace if requested:
|
|
||||||
if (-not $NonDestructive) {
|
|
||||||
Remove-Item -Path function:deactivate
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
<#
|
|
||||||
.Description
|
|
||||||
Get-PyVenvConfig parses the values from the pyvenv.cfg file located in the
|
|
||||||
given folder, and returns them in a map.
|
|
||||||
|
|
||||||
For each line in the pyvenv.cfg file, if that line can be parsed into exactly
|
|
||||||
two strings separated by `=` (with any amount of whitespace surrounding the =)
|
|
||||||
then it is considered a `key = value` line. The left hand string is the key,
|
|
||||||
the right hand is the value.
|
|
||||||
|
|
||||||
If the value starts with a `'` or a `"` then the first and last character is
|
|
||||||
stripped from the value before being captured.
|
|
||||||
|
|
||||||
.Parameter ConfigDir
|
|
||||||
Path to the directory that contains the `pyvenv.cfg` file.
|
|
||||||
#>
|
|
||||||
function Get-PyVenvConfig(
|
|
||||||
[String]
|
|
||||||
$ConfigDir
|
|
||||||
) {
|
|
||||||
Write-Verbose "Given ConfigDir=$ConfigDir, obtain values in pyvenv.cfg"
|
|
||||||
|
|
||||||
# Ensure the file exists, and issue a warning if it doesn't (but still allow the function to continue).
|
|
||||||
$pyvenvConfigPath = Join-Path -Resolve -Path $ConfigDir -ChildPath 'pyvenv.cfg' -ErrorAction Continue
|
|
||||||
|
|
||||||
# An empty map will be returned if no config file is found.
|
|
||||||
$pyvenvConfig = @{ }
|
|
||||||
|
|
||||||
if ($pyvenvConfigPath) {
|
|
||||||
|
|
||||||
Write-Verbose "File exists, parse `key = value` lines"
|
|
||||||
$pyvenvConfigContent = Get-Content -Path $pyvenvConfigPath
|
|
||||||
|
|
||||||
$pyvenvConfigContent | ForEach-Object {
|
|
||||||
$keyval = $PSItem -split "\s*=\s*", 2
|
|
||||||
if ($keyval[0] -and $keyval[1]) {
|
|
||||||
$val = $keyval[1]
|
|
||||||
|
|
||||||
# Remove extraneous quotations around a string value.
|
|
||||||
if ("'""".Contains($val.Substring(0, 1))) {
|
|
||||||
$val = $val.Substring(1, $val.Length - 2)
|
|
||||||
}
|
|
||||||
|
|
||||||
$pyvenvConfig[$keyval[0]] = $val
|
|
||||||
Write-Verbose "Adding Key: '$($keyval[0])'='$val'"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return $pyvenvConfig
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
<# Begin Activate script --------------------------------------------------- #>
|
|
||||||
|
|
||||||
# Determine the containing directory of this script
|
|
||||||
$VenvExecPath = Split-Path -Parent $MyInvocation.MyCommand.Definition
|
|
||||||
$VenvExecDir = Get-Item -Path $VenvExecPath
|
|
||||||
|
|
||||||
Write-Verbose "Activation script is located in path: '$VenvExecPath'"
|
|
||||||
Write-Verbose "VenvExecDir Fullname: '$($VenvExecDir.FullName)"
|
|
||||||
Write-Verbose "VenvExecDir Name: '$($VenvExecDir.Name)"
|
|
||||||
|
|
||||||
# Set values required in priority: CmdLine, ConfigFile, Default
|
|
||||||
# First, get the location of the virtual environment, it might not be
|
|
||||||
# VenvExecDir if specified on the command line.
|
|
||||||
if ($VenvDir) {
|
|
||||||
Write-Verbose "VenvDir given as parameter, using '$VenvDir' to determine values"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Verbose "VenvDir not given as a parameter, using parent directory name as VenvDir."
|
|
||||||
$VenvDir = $VenvExecDir.Parent.FullName.TrimEnd("\\/")
|
|
||||||
Write-Verbose "VenvDir=$VenvDir"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Next, read the `pyvenv.cfg` file to determine any required value such
|
|
||||||
# as `prompt`.
|
|
||||||
$pyvenvCfg = Get-PyVenvConfig -ConfigDir $VenvDir
|
|
||||||
|
|
||||||
# Next, set the prompt from the command line, or the config file, or
|
|
||||||
# just use the name of the virtual environment folder.
|
|
||||||
if ($Prompt) {
|
|
||||||
Write-Verbose "Prompt specified as argument, using '$Prompt'"
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Verbose "Prompt not specified as argument to script, checking pyvenv.cfg value"
|
|
||||||
if ($pyvenvCfg -and $pyvenvCfg['prompt']) {
|
|
||||||
Write-Verbose " Setting based on value in pyvenv.cfg='$($pyvenvCfg['prompt'])'"
|
|
||||||
$Prompt = $pyvenvCfg['prompt'];
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Write-Verbose " Setting prompt based on parent's directory's name. (Is the directory name passed to venv module when creating the virtual environment)"
|
|
||||||
Write-Verbose " Got leaf-name of $VenvDir='$(Split-Path -Path $venvDir -Leaf)'"
|
|
||||||
$Prompt = Split-Path -Path $venvDir -Leaf
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Write-Verbose "Prompt = '$Prompt'"
|
|
||||||
Write-Verbose "VenvDir='$VenvDir'"
|
|
||||||
|
|
||||||
# Deactivate any currently active virtual environment, but leave the
|
|
||||||
# deactivate function in place.
|
|
||||||
deactivate -nondestructive
|
|
||||||
|
|
||||||
# Now set the environment variable VIRTUAL_ENV, used by many tools to determine
|
|
||||||
# that there is an activated venv.
|
|
||||||
$env:VIRTUAL_ENV = $VenvDir
|
|
||||||
|
|
||||||
if (-not $Env:VIRTUAL_ENV_DISABLE_PROMPT) {
|
|
||||||
|
|
||||||
Write-Verbose "Setting prompt to '$Prompt'"
|
|
||||||
|
|
||||||
# Set the prompt to include the env name
|
|
||||||
# Make sure _OLD_VIRTUAL_PROMPT is global
|
|
||||||
function global:_OLD_VIRTUAL_PROMPT { "" }
|
|
||||||
Copy-Item -Path function:prompt -Destination function:_OLD_VIRTUAL_PROMPT
|
|
||||||
New-Variable -Name _PYTHON_VENV_PROMPT_PREFIX -Description "Python virtual environment prompt prefix" -Scope Global -Option ReadOnly -Visibility Public -Value $Prompt
|
|
||||||
|
|
||||||
function global:prompt {
|
|
||||||
Write-Host -NoNewline -ForegroundColor Green "($_PYTHON_VENV_PROMPT_PREFIX) "
|
|
||||||
_OLD_VIRTUAL_PROMPT
|
|
||||||
}
|
|
||||||
$env:VIRTUAL_ENV_PROMPT = $Prompt
|
|
||||||
}
|
|
||||||
|
|
||||||
# Clear PYTHONHOME
|
|
||||||
if (Test-Path -Path Env:PYTHONHOME) {
|
|
||||||
Copy-Item -Path Env:PYTHONHOME -Destination Env:_OLD_VIRTUAL_PYTHONHOME
|
|
||||||
Remove-Item -Path Env:PYTHONHOME
|
|
||||||
}
|
|
||||||
|
|
||||||
# Add the venv to the PATH
|
|
||||||
Copy-Item -Path Env:PATH -Destination Env:_OLD_VIRTUAL_PATH
|
|
||||||
$Env:PATH = "$VenvExecDir$([System.IO.Path]::PathSeparator)$Env:PATH"
|
|
||||||
|
|
||||||
# SIG # Begin signature block
|
|
||||||
# MIIvIgYJKoZIhvcNAQcCoIIvEzCCLw8CAQExDzANBglghkgBZQMEAgEFADB5Bgor
|
|
||||||
# BgEEAYI3AgEEoGswaTA0BgorBgEEAYI3AgEeMCYCAwEAAAQQH8w7YFlLCE63JNLG
|
|
||||||
# KX7zUQIBAAIBAAIBAAIBAAIBADAxMA0GCWCGSAFlAwQCAQUABCBnL745ElCYk8vk
|
|
||||||
# dBtMuQhLeWJ3ZGfzKW4DHCYzAn+QB6CCE8MwggWQMIIDeKADAgECAhAFmxtXno4h
|
|
||||||
# MuI5B72nd3VcMA0GCSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQK
|
|
||||||
# EwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNV
|
|
||||||
# BAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9vdCBHNDAeFw0xMzA4MDExMjAwMDBaFw0z
|
|
||||||
# ODAxMTUxMjAwMDBaMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
|
|
||||||
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
|
|
||||||
# IFRydXN0ZWQgUm9vdCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
|
|
||||||
# AL/mkHNo3rvkXUo8MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/z
|
|
||||||
# G6Q4FutWxpdtHauyefLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZ
|
|
||||||
# anMylNEQRBAu34LzB4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7s
|
|
||||||
# Wxq868nPzaw0QF+xembud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL
|
|
||||||
# 2pNe3I6PgNq2kZhAkHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfb
|
|
||||||
# BHMqbpEBfCFM1LyuGwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3
|
|
||||||
# JFxGj2T3wWmIdph2PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3c
|
|
||||||
# AORFJYm2mkQZK37AlLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqx
|
|
||||||
# YxhElRp2Yn72gLD76GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0
|
|
||||||
# viastkF13nqsX40/ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aL
|
|
||||||
# T8LWRV+dIPyhHsXAj6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjQjBAMA8GA1Ud
|
|
||||||
# EwEB/wQFMAMBAf8wDgYDVR0PAQH/BAQDAgGGMB0GA1UdDgQWBBTs1+OC0nFdZEzf
|
|
||||||
# Lmc/57qYrhwPTzANBgkqhkiG9w0BAQwFAAOCAgEAu2HZfalsvhfEkRvDoaIAjeNk
|
|
||||||
# aA9Wz3eucPn9mkqZucl4XAwMX+TmFClWCzZJXURj4K2clhhmGyMNPXnpbWvWVPjS
|
|
||||||
# PMFDQK4dUPVS/JA7u5iZaWvHwaeoaKQn3J35J64whbn2Z006Po9ZOSJTROvIXQPK
|
|
||||||
# 7VB6fWIhCoDIc2bRoAVgX+iltKevqPdtNZx8WorWojiZ83iL9E3SIAveBO6Mm0eB
|
|
||||||
# cg3AFDLvMFkuruBx8lbkapdvklBtlo1oepqyNhR6BvIkuQkRUNcIsbiJeoQjYUIp
|
|
||||||
# 5aPNoiBB19GcZNnqJqGLFNdMGbJQQXE9P01wI4YMStyB0swylIQNCAmXHE/A7msg
|
|
||||||
# dDDS4Dk0EIUhFQEI6FUy3nFJ2SgXUE3mvk3RdazQyvtBuEOlqtPDBURPLDab4vri
|
|
||||||
# RbgjU2wGb2dVf0a1TD9uKFp5JtKkqGKX0h7i7UqLvBv9R0oN32dmfrJbQdA75PQ7
|
|
||||||
# 9ARj6e/CVABRoIoqyc54zNXqhwQYs86vSYiv85KZtrPmYQ/ShQDnUBrkG5WdGaG5
|
|
||||||
# nLGbsQAe79APT0JsyQq87kP6OnGlyE0mpTX9iV28hWIdMtKgK1TtmlfB2/oQzxm3
|
|
||||||
# i0objwG2J5VT6LaJbVu8aNQj6ItRolb58KaAoNYes7wPD1N1KarqE3fk3oyBIa0H
|
|
||||||
# EEcRrYc9B9F1vM/zZn4wggawMIIEmKADAgECAhAIrUCyYNKcTJ9ezam9k67ZMA0G
|
|
||||||
# CSqGSIb3DQEBDAUAMGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJ
|
|
||||||
# bmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0
|
|
||||||
# IFRydXN0ZWQgUm9vdCBHNDAeFw0yMTA0MjkwMDAwMDBaFw0zNjA0MjgyMzU5NTla
|
|
||||||
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
|
|
||||||
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
|
|
||||||
# ODQgMjAyMSBDQTEwggIiMA0GCSqGSIb3DQEBAQUAA4ICDwAwggIKAoICAQDVtC9C
|
|
||||||
# 0CiteLdd1TlZG7GIQvUzjOs9gZdwxbvEhSYwn6SOaNhc9es0JAfhS0/TeEP0F9ce
|
|
||||||
# 2vnS1WcaUk8OoVf8iJnBkcyBAz5NcCRks43iCH00fUyAVxJrQ5qZ8sU7H/Lvy0da
|
|
||||||
# E6ZMswEgJfMQ04uy+wjwiuCdCcBlp/qYgEk1hz1RGeiQIXhFLqGfLOEYwhrMxe6T
|
|
||||||
# SXBCMo/7xuoc82VokaJNTIIRSFJo3hC9FFdd6BgTZcV/sk+FLEikVoQ11vkunKoA
|
|
||||||
# FdE3/hoGlMJ8yOobMubKwvSnowMOdKWvObarYBLj6Na59zHh3K3kGKDYwSNHR7Oh
|
|
||||||
# D26jq22YBoMbt2pnLdK9RBqSEIGPsDsJ18ebMlrC/2pgVItJwZPt4bRc4G/rJvmM
|
|
||||||
# 1bL5OBDm6s6R9b7T+2+TYTRcvJNFKIM2KmYoX7BzzosmJQayg9Rc9hUZTO1i4F4z
|
|
||||||
# 8ujo7AqnsAMrkbI2eb73rQgedaZlzLvjSFDzd5Ea/ttQokbIYViY9XwCFjyDKK05
|
|
||||||
# huzUtw1T0PhH5nUwjewwk3YUpltLXXRhTT8SkXbev1jLchApQfDVxW0mdmgRQRNY
|
|
||||||
# mtwmKwH0iU1Z23jPgUo+QEdfyYFQc4UQIyFZYIpkVMHMIRroOBl8ZhzNeDhFMJlP
|
|
||||||
# /2NPTLuqDQhTQXxYPUez+rbsjDIJAsxsPAxWEQIDAQABo4IBWTCCAVUwEgYDVR0T
|
|
||||||
# AQH/BAgwBgEB/wIBADAdBgNVHQ4EFgQUaDfg67Y7+F8Rhvv+YXsIiGX0TkIwHwYD
|
|
||||||
# VR0jBBgwFoAU7NfjgtJxXWRM3y5nP+e6mK4cD08wDgYDVR0PAQH/BAQDAgGGMBMG
|
|
||||||
# A1UdJQQMMAoGCCsGAQUFBwMDMHcGCCsGAQUFBwEBBGswaTAkBggrBgEFBQcwAYYY
|
|
||||||
# aHR0cDovL29jc3AuZGlnaWNlcnQuY29tMEEGCCsGAQUFBzAChjVodHRwOi8vY2Fj
|
|
||||||
# ZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkUm9vdEc0LmNydDBDBgNV
|
|
||||||
# HR8EPDA6MDigNqA0hjJodHRwOi8vY3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRU
|
|
||||||
# cnVzdGVkUm9vdEc0LmNybDAcBgNVHSAEFTATMAcGBWeBDAEDMAgGBmeBDAEEATAN
|
|
||||||
# BgkqhkiG9w0BAQwFAAOCAgEAOiNEPY0Idu6PvDqZ01bgAhql+Eg08yy25nRm95Ry
|
|
||||||
# sQDKr2wwJxMSnpBEn0v9nqN8JtU3vDpdSG2V1T9J9Ce7FoFFUP2cvbaF4HZ+N3HL
|
|
||||||
# IvdaqpDP9ZNq4+sg0dVQeYiaiorBtr2hSBh+3NiAGhEZGM1hmYFW9snjdufE5Btf
|
|
||||||
# Q/g+lP92OT2e1JnPSt0o618moZVYSNUa/tcnP/2Q0XaG3RywYFzzDaju4ImhvTnh
|
|
||||||
# OE7abrs2nfvlIVNaw8rpavGiPttDuDPITzgUkpn13c5UbdldAhQfQDN8A+KVssIh
|
|
||||||
# dXNSy0bYxDQcoqVLjc1vdjcshT8azibpGL6QB7BDf5WIIIJw8MzK7/0pNVwfiThV
|
|
||||||
# 9zeKiwmhywvpMRr/LhlcOXHhvpynCgbWJme3kuZOX956rEnPLqR0kq3bPKSchh/j
|
|
||||||
# wVYbKyP/j7XqiHtwa+aguv06P0WmxOgWkVKLQcBIhEuWTatEQOON8BUozu3xGFYH
|
|
||||||
# Ki8QxAwIZDwzj64ojDzLj4gLDb879M4ee47vtevLt/B3E+bnKD+sEq6lLyJsQfmC
|
|
||||||
# XBVmzGwOysWGw/YmMwwHS6DTBwJqakAwSEs0qFEgu60bhQjiWQ1tygVQK+pKHJ6l
|
|
||||||
# /aCnHwZ05/LWUpD9r4VIIflXO7ScA+2GRfS0YW6/aOImYIbqyK+p/pQd52MbOoZW
|
|
||||||
# eE4wggd3MIIFX6ADAgECAhAHHxQbizANJfMU6yMM0NHdMA0GCSqGSIb3DQEBCwUA
|
|
||||||
# MGkxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5jLjFBMD8GA1UE
|
|
||||||
# AxM4RGlnaUNlcnQgVHJ1c3RlZCBHNCBDb2RlIFNpZ25pbmcgUlNBNDA5NiBTSEEz
|
|
||||||
# ODQgMjAyMSBDQTEwHhcNMjIwMTE3MDAwMDAwWhcNMjUwMTE1MjM1OTU5WjB8MQsw
|
|
||||||
# CQYDVQQGEwJVUzEPMA0GA1UECBMGT3JlZ29uMRIwEAYDVQQHEwlCZWF2ZXJ0b24x
|
|
||||||
# IzAhBgNVBAoTGlB5dGhvbiBTb2Z0d2FyZSBGb3VuZGF0aW9uMSMwIQYDVQQDExpQ
|
|
||||||
# eXRob24gU29mdHdhcmUgRm91bmRhdGlvbjCCAiIwDQYJKoZIhvcNAQEBBQADggIP
|
|
||||||
# ADCCAgoCggIBAKgc0BTT+iKbtK6f2mr9pNMUTcAJxKdsuOiSYgDFfwhjQy89koM7
|
|
||||||
# uP+QV/gwx8MzEt3c9tLJvDccVWQ8H7mVsk/K+X+IufBLCgUi0GGAZUegEAeRlSXx
|
|
||||||
# xhYScr818ma8EvGIZdiSOhqjYc4KnfgfIS4RLtZSrDFG2tN16yS8skFa3IHyvWdb
|
|
||||||
# D9PvZ4iYNAS4pjYDRjT/9uzPZ4Pan+53xZIcDgjiTwOh8VGuppxcia6a7xCyKoOA
|
|
||||||
# GjvCyQsj5223v1/Ig7Dp9mGI+nh1E3IwmyTIIuVHyK6Lqu352diDY+iCMpk9Zanm
|
|
||||||
# SjmB+GMVs+H/gOiofjjtf6oz0ki3rb7sQ8fTnonIL9dyGTJ0ZFYKeb6BLA66d2GA
|
|
||||||
# LwxZhLe5WH4Np9HcyXHACkppsE6ynYjTOd7+jN1PRJahN1oERzTzEiV6nCO1M3U1
|
|
||||||
# HbPTGyq52IMFSBM2/07WTJSbOeXjvYR7aUxK9/ZkJiacl2iZI7IWe7JKhHohqKuc
|
|
||||||
# eQNyOzxTakLcRkzynvIrk33R9YVqtB4L6wtFxhUjvDnQg16xot2KVPdfyPAWd81w
|
|
||||||
# tZADmrUtsZ9qG79x1hBdyOl4vUtVPECuyhCxaw+faVjumapPUnwo8ygflJJ74J+B
|
|
||||||
# Yxf6UuD7m8yzsfXWkdv52DjL74TxzuFTLHPyARWCSCAbzn3ZIly+qIqDAgMBAAGj
|
|
||||||
# ggIGMIICAjAfBgNVHSMEGDAWgBRoN+Drtjv4XxGG+/5hewiIZfROQjAdBgNVHQ4E
|
|
||||||
# FgQUt/1Teh2XDuUj2WW3siYWJgkZHA8wDgYDVR0PAQH/BAQDAgeAMBMGA1UdJQQM
|
|
||||||
# MAoGCCsGAQUFBwMDMIG1BgNVHR8Ega0wgaowU6BRoE+GTWh0dHA6Ly9jcmwzLmRp
|
|
||||||
# Z2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWduaW5nUlNBNDA5NlNI
|
|
||||||
# QTM4NDIwMjFDQTEuY3JsMFOgUaBPhk1odHRwOi8vY3JsNC5kaWdpY2VydC5jb20v
|
|
||||||
# RGlnaUNlcnRUcnVzdGVkRzRDb2RlU2lnbmluZ1JTQTQwOTZTSEEzODQyMDIxQ0Ex
|
|
||||||
# LmNybDA+BgNVHSAENzA1MDMGBmeBDAEEATApMCcGCCsGAQUFBwIBFhtodHRwOi8v
|
|
||||||
# d3d3LmRpZ2ljZXJ0LmNvbS9DUFMwgZQGCCsGAQUFBwEBBIGHMIGEMCQGCCsGAQUF
|
|
||||||
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wXAYIKwYBBQUHMAKGUGh0dHA6
|
|
||||||
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRHNENvZGVTaWdu
|
|
||||||
# aW5nUlNBNDA5NlNIQTM4NDIwMjFDQTEuY3J0MAwGA1UdEwEB/wQCMAAwDQYJKoZI
|
|
||||||
# hvcNAQELBQADggIBABxv4AeV/5ltkELHSC63fXAFYS5tadcWTiNc2rskrNLrfH1N
|
|
||||||
# s0vgSZFoQxYBFKI159E8oQQ1SKbTEubZ/B9kmHPhprHya08+VVzxC88pOEvz68nA
|
|
||||||
# 82oEM09584aILqYmj8Pj7h/kmZNzuEL7WiwFa/U1hX+XiWfLIJQsAHBla0i7QRF2
|
|
||||||
# de8/VSF0XXFa2kBQ6aiTsiLyKPNbaNtbcucaUdn6vVUS5izWOXM95BSkFSKdE45O
|
|
||||||
# q3FForNJXjBvSCpwcP36WklaHL+aHu1upIhCTUkzTHMh8b86WmjRUqbrnvdyR2yd
|
|
||||||
# I5l1OqcMBjkpPpIV6wcc+KY/RH2xvVuuoHjlUjwq2bHiNoX+W1scCpnA8YTs2d50
|
|
||||||
# jDHUgwUo+ciwpffH0Riq132NFmrH3r67VaN3TuBxjI8SIZM58WEDkbeoriDk3hxU
|
|
||||||
# 8ZWV7b8AW6oyVBGfM06UgkfMb58h+tJPrFx8VI/WLq1dTqMfZOm5cuclMnUHs2uq
|
|
||||||
# rRNtnV8UfidPBL4ZHkTcClQbCoz0UbLhkiDvIS00Dn+BBcxw/TKqVL4Oaz3bkMSs
|
|
||||||
# M46LciTeucHY9ExRVt3zy7i149sd+F4QozPqn7FrSVHXmem3r7bjyHTxOgqxRCVa
|
|
||||||
# 18Vtx7P/8bYSBeS+WHCKcliFCecspusCDSlnRUjZwyPdP0VHxaZg2unjHY3rMYIa
|
|
||||||
# tTCCGrECAQEwfTBpMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGlnaUNlcnQsIElu
|
|
||||||
# Yy4xQTA/BgNVBAMTOERpZ2lDZXJ0IFRydXN0ZWQgRzQgQ29kZSBTaWduaW5nIFJT
|
|
||||||
# QTQwOTYgU0hBMzg0IDIwMjEgQ0ExAhAHHxQbizANJfMU6yMM0NHdMA0GCWCGSAFl
|
|
||||||
# AwQCAQUAoIHIMBkGCSqGSIb3DQEJAzEMBgorBgEEAYI3AgEEMBwGCisGAQQBgjcC
|
|
||||||
# AQsxDjAMBgorBgEEAYI3AgEVMC8GCSqGSIb3DQEJBDEiBCBnAZ6P7YvTwq0fbF62
|
|
||||||
# o7E75R0LxsW5OtyYiFESQckLhjBcBgorBgEEAYI3AgEMMU4wTKBGgEQAQgB1AGkA
|
|
||||||
# bAB0ADoAIABSAGUAbABlAGEAcwBlAF8AdgAzAC4AMQAxAC4ANABfADIAMAAyADMA
|
|
||||||
# MAA2ADAANwAuADAAMaECgAAwDQYJKoZIhvcNAQEBBQAEggIAVdxtEr9NH8SoVTzT
|
|
||||||
# o/jdr3t1yqExSecge3YGCu9USfMqLtmCKzG5r2rf3xZkJ6CpvmHwji3FUY6Hl991
|
|
||||||
# Ttd0eEEpjeEse9gotnojgHTQACJntGuPcK+65jIQYNvp3JIuczjTW0JjWkJf4lqI
|
|
||||||
# hVS6rEc00D/0NsUF9BbNkjNZ0AUQeOWe2WZJnqRRFN4U3pToN51NDjpEtRjlNTkc
|
|
||||||
# SzoNO7ZyEsSXkNenlgbgS1yXEQ8v4bbnbPyyL+2yWMG1QsLv6M3OV0kXx9aow1r5
|
|
||||||
# gZ1mCjBkbtWKH58WVBoepUaPYTjFBWCT2pDrorbg6cguwBdyz7s8X+WlCD4ycFfW
|
|
||||||
# o95x7u1W9RwPPPppszr8Pd4jZSbEXEQ/G9Ke5NvTvNmK93b7/kySfNYfwW2meP6E
|
|
||||||
# JIc0R9DMSZlK+ChtU5mmvo4e6YQTLXIXQhPIz7jVNlUjXMJX7WALjE72EDdC5MpQ
|
|
||||||
# ygW7wue6EhjlUVXT4pEIySCGaXxUzRi1oh+Q+Jbe3rDvhSPZUWzCqEtOkJ35dLYh
|
|
||||||
# D9Rahi2BM1qaepfu1wVtSXbVbc0SDPjloojEmTyDnk61u5epo0E0oHqNAU8t1ZTN
|
|
||||||
# +Guptl/agMp52uRsaC5Bi276icqRtclfx9E4SfJEiw7xRlImCclMpw2dRsyzIrpb
|
|
||||||
# MKpWDAno4rClgYS3M9lqQ71RlXehghc+MIIXOgYKKwYBBAGCNwMDATGCFyowghcm
|
|
||||||
# BgkqhkiG9w0BBwKgghcXMIIXEwIBAzEPMA0GCWCGSAFlAwQCAQUAMHgGCyqGSIb3
|
|
||||||
# DQEJEAEEoGkEZzBlAgEBBglghkgBhv1sBwEwMTANBglghkgBZQMEAgEFAAQgsPGH
|
|
||||||
# UIiYgGXi/94WNZrP+V1kV/B5SVJn3ck+XzTJ0aACEQCJ79BpOkCDCW06IgZOU3EQ
|
|
||||||
# GA8yMDIzMDYwNzA1NTc0NFqgghMHMIIGwDCCBKigAwIBAgIQDE1pckuU+jwqSj0p
|
|
||||||
# B4A9WjANBgkqhkiG9w0BAQsFADBjMQswCQYDVQQGEwJVUzEXMBUGA1UEChMORGln
|
|
||||||
# aUNlcnQsIEluYy4xOzA5BgNVBAMTMkRpZ2lDZXJ0IFRydXN0ZWQgRzQgUlNBNDA5
|
|
||||||
# NiBTSEEyNTYgVGltZVN0YW1waW5nIENBMB4XDTIyMDkyMTAwMDAwMFoXDTMzMTEy
|
|
||||||
# MTIzNTk1OVowRjELMAkGA1UEBhMCVVMxETAPBgNVBAoTCERpZ2lDZXJ0MSQwIgYD
|
|
||||||
# VQQDExtEaWdpQ2VydCBUaW1lc3RhbXAgMjAyMiAtIDIwggIiMA0GCSqGSIb3DQEB
|
|
||||||
# AQUAA4ICDwAwggIKAoICAQDP7KUmOsap8mu7jcENmtuh6BSFdDMaJqzQHFUeHjZt
|
|
||||||
# vJJVDGH0nQl3PRWWCC9rZKT9BoMW15GSOBwxApb7crGXOlWvM+xhiummKNuQY1y9
|
|
||||||
# iVPgOi2Mh0KuJqTku3h4uXoW4VbGwLpkU7sqFudQSLuIaQyIxvG+4C99O7HKU41A
|
|
||||||
# gx7ny3JJKB5MgB6FVueF7fJhvKo6B332q27lZt3iXPUv7Y3UTZWEaOOAy2p50dIQ
|
|
||||||
# kUYp6z4m8rSMzUy5Zsi7qlA4DeWMlF0ZWr/1e0BubxaompyVR4aFeT4MXmaMGgok
|
|
||||||
# vpyq0py2909ueMQoP6McD1AGN7oI2TWmtR7aeFgdOej4TJEQln5N4d3CraV++C0b
|
|
||||||
# H+wrRhijGfY59/XBT3EuiQMRoku7mL/6T+R7Nu8GRORV/zbq5Xwx5/PCUsTmFnta
|
|
||||||
# fqUlc9vAapkhLWPlWfVNL5AfJ7fSqxTlOGaHUQhr+1NDOdBk+lbP4PQK5hRtZHi7
|
|
||||||
# mP2Uw3Mh8y/CLiDXgazT8QfU4b3ZXUtuMZQpi+ZBpGWUwFjl5S4pkKa3YWT62SBs
|
|
||||||
# GFFguqaBDwklU/G/O+mrBw5qBzliGcnWhX8T2Y15z2LF7OF7ucxnEweawXjtxojI
|
|
||||||
# sG4yeccLWYONxu71LHx7jstkifGxxLjnU15fVdJ9GSlZA076XepFcxyEftfO4tQ6
|
|
||||||
# dwIDAQABo4IBizCCAYcwDgYDVR0PAQH/BAQDAgeAMAwGA1UdEwEB/wQCMAAwFgYD
|
|
||||||
# VR0lAQH/BAwwCgYIKwYBBQUHAwgwIAYDVR0gBBkwFzAIBgZngQwBBAIwCwYJYIZI
|
|
||||||
# AYb9bAcBMB8GA1UdIwQYMBaAFLoW2W1NhS9zKXaaL3WMaiCPnshvMB0GA1UdDgQW
|
|
||||||
# BBRiit7QYfyPMRTtlwvNPSqUFN9SnDBaBgNVHR8EUzBRME+gTaBLhklodHRwOi8v
|
|
||||||
# Y3JsMy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hBMjU2
|
|
||||||
# VGltZVN0YW1waW5nQ0EuY3JsMIGQBggrBgEFBQcBAQSBgzCBgDAkBggrBgEFBQcw
|
|
||||||
# AYYYaHR0cDovL29jc3AuZGlnaWNlcnQuY29tMFgGCCsGAQUFBzAChkxodHRwOi8v
|
|
||||||
# Y2FjZXJ0cy5kaWdpY2VydC5jb20vRGlnaUNlcnRUcnVzdGVkRzRSU0E0MDk2U0hB
|
|
||||||
# MjU2VGltZVN0YW1waW5nQ0EuY3J0MA0GCSqGSIb3DQEBCwUAA4ICAQBVqioa80bz
|
|
||||||
# eFc3MPx140/WhSPx/PmVOZsl5vdyipjDd9Rk/BX7NsJJUSx4iGNVCUY5APxp1Mqb
|
|
||||||
# KfujP8DJAJsTHbCYidx48s18hc1Tna9i4mFmoxQqRYdKmEIrUPwbtZ4IMAn65C3X
|
|
||||||
# CYl5+QnmiM59G7hqopvBU2AJ6KO4ndetHxy47JhB8PYOgPvk/9+dEKfrALpfSo8a
|
|
||||||
# OlK06r8JSRU1NlmaD1TSsht/fl4JrXZUinRtytIFZyt26/+YsiaVOBmIRBTlClmi
|
|
||||||
# a+ciPkQh0j8cwJvtfEiy2JIMkU88ZpSvXQJT657inuTTH4YBZJwAwuladHUNPeF5
|
|
||||||
# iL8cAZfJGSOA1zZaX5YWsWMMxkZAO85dNdRZPkOaGK7DycvD+5sTX2q1x+DzBcNZ
|
|
||||||
# 3ydiK95ByVO5/zQQZ/YmMph7/lxClIGUgp2sCovGSxVK05iQRWAzgOAj3vgDpPZF
|
|
||||||
# R+XOuANCR+hBNnF3rf2i6Jd0Ti7aHh2MWsgemtXC8MYiqE+bvdgcmlHEL5r2X6cn
|
|
||||||
# l7qWLoVXwGDneFZ/au/ClZpLEQLIgpzJGgV8unG1TnqZbPTontRamMifv427GFxD
|
|
||||||
# 9dAq6OJi7ngE273R+1sKqHB+8JeEeOMIA11HLGOoJTiXAdI/Otrl5fbmm9x+LMz/
|
|
||||||
# F0xNAKLY1gEOuIvu5uByVYksJxlh9ncBjDCCBq4wggSWoAMCAQICEAc2N7ckVHzY
|
|
||||||
# R6z9KGYqXlswDQYJKoZIhvcNAQELBQAwYjELMAkGA1UEBhMCVVMxFTATBgNVBAoT
|
|
||||||
# DERpZ2lDZXJ0IEluYzEZMBcGA1UECxMQd3d3LmRpZ2ljZXJ0LmNvbTEhMB8GA1UE
|
|
||||||
# AxMYRGlnaUNlcnQgVHJ1c3RlZCBSb290IEc0MB4XDTIyMDMyMzAwMDAwMFoXDTM3
|
|
||||||
# MDMyMjIzNTk1OVowYzELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDkRpZ2lDZXJ0LCBJ
|
|
||||||
# bmMuMTswOQYDVQQDEzJEaWdpQ2VydCBUcnVzdGVkIEc0IFJTQTQwOTYgU0hBMjU2
|
|
||||||
# IFRpbWVTdGFtcGluZyBDQTCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIB
|
|
||||||
# AMaGNQZJs8E9cklRVcclA8TykTepl1Gh1tKD0Z5Mom2gsMyD+Vr2EaFEFUJfpIjz
|
|
||||||
# aPp985yJC3+dH54PMx9QEwsmc5Zt+FeoAn39Q7SE2hHxc7Gz7iuAhIoiGN/r2j3E
|
|
||||||
# F3+rGSs+QtxnjupRPfDWVtTnKC3r07G1decfBmWNlCnT2exp39mQh0YAe9tEQYnc
|
|
||||||
# fGpXevA3eZ9drMvohGS0UvJ2R/dhgxndX7RUCyFobjchu0CsX7LeSn3O9TkSZ+8O
|
|
||||||
# pWNs5KbFHc02DVzV5huowWR0QKfAcsW6Th+xtVhNef7Xj3OTrCw54qVI1vCwMROp
|
|
||||||
# VymWJy71h6aPTnYVVSZwmCZ/oBpHIEPjQ2OAe3VuJyWQmDo4EbP29p7mO1vsgd4i
|
|
||||||
# FNmCKseSv6De4z6ic/rnH1pslPJSlRErWHRAKKtzQ87fSqEcazjFKfPKqpZzQmif
|
|
||||||
# tkaznTqj1QPgv/CiPMpC3BhIfxQ0z9JMq++bPf4OuGQq+nUoJEHtQr8FnGZJUlD0
|
|
||||||
# UfM2SU2LINIsVzV5K6jzRWC8I41Y99xh3pP+OcD5sjClTNfpmEpYPtMDiP6zj9Ne
|
|
||||||
# S3YSUZPJjAw7W4oiqMEmCPkUEBIDfV8ju2TjY+Cm4T72wnSyPx4JduyrXUZ14mCj
|
|
||||||
# WAkBKAAOhFTuzuldyF4wEr1GnrXTdrnSDmuZDNIztM2xAgMBAAGjggFdMIIBWTAS
|
|
||||||
# BgNVHRMBAf8ECDAGAQH/AgEAMB0GA1UdDgQWBBS6FtltTYUvcyl2mi91jGogj57I
|
|
||||||
# bzAfBgNVHSMEGDAWgBTs1+OC0nFdZEzfLmc/57qYrhwPTzAOBgNVHQ8BAf8EBAMC
|
|
||||||
# AYYwEwYDVR0lBAwwCgYIKwYBBQUHAwgwdwYIKwYBBQUHAQEEazBpMCQGCCsGAQUF
|
|
||||||
# BzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQQYIKwYBBQUHMAKGNWh0dHA6
|
|
||||||
# Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydFRydXN0ZWRSb290RzQuY3J0
|
|
||||||
# MEMGA1UdHwQ8MDowOKA2oDSGMmh0dHA6Ly9jcmwzLmRpZ2ljZXJ0LmNvbS9EaWdp
|
|
||||||
# Q2VydFRydXN0ZWRSb290RzQuY3JsMCAGA1UdIAQZMBcwCAYGZ4EMAQQCMAsGCWCG
|
|
||||||
# SAGG/WwHATANBgkqhkiG9w0BAQsFAAOCAgEAfVmOwJO2b5ipRCIBfmbW2CFC4bAY
|
|
||||||
# LhBNE88wU86/GPvHUF3iSyn7cIoNqilp/GnBzx0H6T5gyNgL5Vxb122H+oQgJTQx
|
|
||||||
# Z822EpZvxFBMYh0MCIKoFr2pVs8Vc40BIiXOlWk/R3f7cnQU1/+rT4osequFzUNf
|
|
||||||
# 7WC2qk+RZp4snuCKrOX9jLxkJodskr2dfNBwCnzvqLx1T7pa96kQsl3p/yhUifDV
|
|
||||||
# inF2ZdrM8HKjI/rAJ4JErpknG6skHibBt94q6/aesXmZgaNWhqsKRcnfxI2g55j7
|
|
||||||
# +6adcq/Ex8HBanHZxhOACcS2n82HhyS7T6NJuXdmkfFynOlLAlKnN36TU6w7HQhJ
|
|
||||||
# D5TNOXrd/yVjmScsPT9rp/Fmw0HNT7ZAmyEhQNC3EyTN3B14OuSereU0cZLXJmvk
|
|
||||||
# OHOrpgFPvT87eK1MrfvElXvtCl8zOYdBeHo46Zzh3SP9HSjTx/no8Zhf+yvYfvJG
|
|
||||||
# nXUsHicsJttvFXseGYs2uJPU5vIXmVnKcPA3v5gA3yAWTyf7YGcWoWa63VXAOimG
|
|
||||||
# sJigK+2VQbc61RWYMbRiCQ8KvYHZE/6/pNHzV9m8BPqC3jLfBInwAM1dwvnQI38A
|
|
||||||
# C+R2AibZ8GV2QqYphwlHK+Z/GqSFD/yYlvZVVCsfgPrA8g4r5db7qS9EFUrnEw4d
|
|
||||||
# 2zc4GqEr9u3WfPwwggWNMIIEdaADAgECAhAOmxiO+dAt5+/bUOIIQBhaMA0GCSqG
|
|
||||||
# SIb3DQEBDAUAMGUxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMx
|
|
||||||
# GTAXBgNVBAsTEHd3dy5kaWdpY2VydC5jb20xJDAiBgNVBAMTG0RpZ2lDZXJ0IEFz
|
|
||||||
# c3VyZWQgSUQgUm9vdCBDQTAeFw0yMjA4MDEwMDAwMDBaFw0zMTExMDkyMzU5NTla
|
|
||||||
# MGIxCzAJBgNVBAYTAlVTMRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsT
|
|
||||||
# EHd3dy5kaWdpY2VydC5jb20xITAfBgNVBAMTGERpZ2lDZXJ0IFRydXN0ZWQgUm9v
|
|
||||||
# dCBHNDCCAiIwDQYJKoZIhvcNAQEBBQADggIPADCCAgoCggIBAL/mkHNo3rvkXUo8
|
|
||||||
# MCIwaTPswqclLskhPfKK2FnC4SmnPVirdprNrnsbhA3EMB/zG6Q4FutWxpdtHauy
|
|
||||||
# efLKEdLkX9YFPFIPUh/GnhWlfr6fqVcWWVVyr2iTcMKyunWZanMylNEQRBAu34Lz
|
|
||||||
# B4TmdDttceItDBvuINXJIB1jKS3O7F5OyJP4IWGbNOsFxl7sWxq868nPzaw0QF+x
|
|
||||||
# embud8hIqGZXV59UWI4MK7dPpzDZVu7Ke13jrclPXuU15zHL2pNe3I6PgNq2kZhA
|
|
||||||
# kHnDeMe2scS1ahg4AxCN2NQ3pC4FfYj1gj4QkXCrVYJBMtfbBHMqbpEBfCFM1Lyu
|
|
||||||
# GwN1XXhm2ToxRJozQL8I11pJpMLmqaBn3aQnvKFPObURWBf3JFxGj2T3wWmIdph2
|
|
||||||
# PVldQnaHiZdpekjw4KISG2aadMreSx7nDmOu5tTvkpI6nj3cAORFJYm2mkQZK37A
|
|
||||||
# lLTSYW3rM9nF30sEAMx9HJXDj/chsrIRt7t/8tWMcCxBYKqxYxhElRp2Yn72gLD7
|
|
||||||
# 6GSmM9GJB+G9t+ZDpBi4pncB4Q+UDCEdslQpJYls5Q5SUUd0viastkF13nqsX40/
|
|
||||||
# ybzTQRESW+UQUOsxxcpyFiIJ33xMdT9j7CFfxCBRa2+xq4aLT8LWRV+dIPyhHsXA
|
|
||||||
# j6KxfgommfXkaS+YHS312amyHeUbAgMBAAGjggE6MIIBNjAPBgNVHRMBAf8EBTAD
|
|
||||||
# AQH/MB0GA1UdDgQWBBTs1+OC0nFdZEzfLmc/57qYrhwPTzAfBgNVHSMEGDAWgBRF
|
|
||||||
# 66Kv9JLLgjEtUYunpyGd823IDzAOBgNVHQ8BAf8EBAMCAYYweQYIKwYBBQUHAQEE
|
|
||||||
# bTBrMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdpY2VydC5jb20wQwYIKwYB
|
|
||||||
# BQUHMAKGN2h0dHA6Ly9jYWNlcnRzLmRpZ2ljZXJ0LmNvbS9EaWdpQ2VydEFzc3Vy
|
|
||||||
# ZWRJRFJvb3RDQS5jcnQwRQYDVR0fBD4wPDA6oDigNoY0aHR0cDovL2NybDMuZGln
|
|
||||||
# aWNlcnQuY29tL0RpZ2lDZXJ0QXNzdXJlZElEUm9vdENBLmNybDARBgNVHSAECjAI
|
|
||||||
# MAYGBFUdIAAwDQYJKoZIhvcNAQEMBQADggEBAHCgv0NcVec4X6CjdBs9thbX979X
|
|
||||||
# B72arKGHLOyFXqkauyL4hxppVCLtpIh3bb0aFPQTSnovLbc47/T/gLn4offyct4k
|
|
||||||
# vFIDyE7QKt76LVbP+fT3rDB6mouyXtTP0UNEm0Mh65ZyoUi0mcudT6cGAxN3J0TU
|
|
||||||
# 53/oWajwvy8LpunyNDzs9wPHh6jSTEAZNUZqaVSwuKFWjuyk1T3osdz9HNj0d1pc
|
|
||||||
# VIxv76FQPfx2CWiEn2/K2yCNNWAcAgPLILCsWKAOQGPFmCLBsln1VWvPJ6tsds5v
|
|
||||||
# Iy30fnFqI2si/xK4VC0nftg62fC2h5b9W9FcrBjDTZ9ztwGpn1eqXijiuZQxggN2
|
|
||||||
# MIIDcgIBATB3MGMxCzAJBgNVBAYTAlVTMRcwFQYDVQQKEw5EaWdpQ2VydCwgSW5j
|
|
||||||
# LjE7MDkGA1UEAxMyRGlnaUNlcnQgVHJ1c3RlZCBHNCBSU0E0MDk2IFNIQTI1NiBU
|
|
||||||
# aW1lU3RhbXBpbmcgQ0ECEAxNaXJLlPo8Kko9KQeAPVowDQYJYIZIAWUDBAIBBQCg
|
|
||||||
# gdEwGgYJKoZIhvcNAQkDMQ0GCyqGSIb3DQEJEAEEMBwGCSqGSIb3DQEJBTEPFw0y
|
|
||||||
# MzA2MDcwNTU3NDRaMCsGCyqGSIb3DQEJEAIMMRwwGjAYMBYEFPOHIk2GM4KSNamU
|
|
||||||
# vL2Plun+HHxzMC8GCSqGSIb3DQEJBDEiBCAZCWWaBjTHAZnsndSxyxCaZSOrTyqo
|
|
||||||
# O35hv3VOlS9KHDA3BgsqhkiG9w0BCRACLzEoMCYwJDAiBCDH9OG+MiiJIKviJjq+
|
|
||||||
# GsT8T+Z4HC1k0EyAdVegI7W2+jANBgkqhkiG9w0BAQEFAASCAgBt92vHxbHXh4Z2
|
|
||||||
# yl+aTo7PltgPhZhoQCWg+gDSyySEqkDN+kTuoW3ROuMjR1JR0htJOwVqnmI/enhW
|
|
||||||
# r8VJiDKfGOGupHEfzAlMaIIC+K+C3sSoeaRR1aiOWrUA/oPpJIgwXyfo65Hf07qf
|
|
||||||
# wdn//4y5zv6oMdHNtpSfFgibze5BjNRAgOUxl9rvKArEN7B+WTCnvLWw/EJe48MQ
|
|
||||||
# B0zUbVFIORQUHlLnCL07JGRSN5bHaMtnn5eEwZFC9522kJaHyLrmfeP4jZLMhjhn
|
|
||||||
# fGxv69HVzggM8CpjpQA8l8hh6Il48TDMZpdqkxwjoRmJVwt3hwTrfuE11NFrXEAD
|
|
||||||
# 8dAAta6N/M722c3BE6UxM2R4QXyV05BL6e4jVJm1aR1ebUVS4nZVZ/jbCexR/+vx
|
|
||||||
# mfSh1SezU3KlgRMDrLF+El883BFoe/99p4/QjjnELhn41lPPAYGefhMI9ioYZULQ
|
|
||||||
# xMyG6qIPA8s2tnYIL/AKvh7SUgFVtOsTKbTFXMMr20sipBQQFUOb8ZD+8u4Iyc4M
|
|
||||||
# UC4d2S6z9zwlPbSr1lk9m3R8rl+j2/VkB1S21nqda3xWFk/+n/2oEJe4gUkCiQxs
|
|
||||||
# qFaykkcAhWYdZVRRNM89ZF23DeYAEkUEaD2M1ld0CZNtvtPNmv/NZV/Xbb3H0RPR
|
|
||||||
# yDBB2JQI1BbEjl7HWy616MUsAqWA+Q==
|
|
||||||
# SIG # End signature block
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
# This file must be used with "source bin/activate" *from bash*
|
|
||||||
# you cannot run it directly
|
|
||||||
|
|
||||||
deactivate () {
|
|
||||||
# reset old environment variables
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PATH:-}" ] ; then
|
|
||||||
PATH="${_OLD_VIRTUAL_PATH:-}"
|
|
||||||
export PATH
|
|
||||||
unset _OLD_VIRTUAL_PATH
|
|
||||||
fi
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PYTHONHOME:-}" ] ; then
|
|
||||||
PYTHONHOME="${_OLD_VIRTUAL_PYTHONHOME:-}"
|
|
||||||
export PYTHONHOME
|
|
||||||
unset _OLD_VIRTUAL_PYTHONHOME
|
|
||||||
fi
|
|
||||||
|
|
||||||
# This should detect bash and zsh, which have a hash command that must
|
|
||||||
# be called to get it to forget past commands. Without forgetting
|
|
||||||
# past commands the $PATH changes we made may not be respected
|
|
||||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
|
||||||
hash -r 2> /dev/null
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -n "${_OLD_VIRTUAL_PS1:-}" ] ; then
|
|
||||||
PS1="${_OLD_VIRTUAL_PS1:-}"
|
|
||||||
export PS1
|
|
||||||
unset _OLD_VIRTUAL_PS1
|
|
||||||
fi
|
|
||||||
|
|
||||||
unset VIRTUAL_ENV
|
|
||||||
unset VIRTUAL_ENV_PROMPT
|
|
||||||
if [ ! "${1:-}" = "nondestructive" ] ; then
|
|
||||||
# Self destruct!
|
|
||||||
unset -f deactivate
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
# unset irrelevant variables
|
|
||||||
deactivate nondestructive
|
|
||||||
|
|
||||||
VIRTUAL_ENV="C:\project\Kintone\KintoneAppBuilder\backend"
|
|
||||||
export VIRTUAL_ENV
|
|
||||||
|
|
||||||
_OLD_VIRTUAL_PATH="$PATH"
|
|
||||||
PATH="$VIRTUAL_ENV/Scripts:$PATH"
|
|
||||||
export PATH
|
|
||||||
|
|
||||||
# unset PYTHONHOME if set
|
|
||||||
# this will fail if PYTHONHOME is set to the empty string (which is bad anyway)
|
|
||||||
# could use `if (set -u; : $PYTHONHOME) ;` in bash
|
|
||||||
if [ -n "${PYTHONHOME:-}" ] ; then
|
|
||||||
_OLD_VIRTUAL_PYTHONHOME="${PYTHONHOME:-}"
|
|
||||||
unset PYTHONHOME
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [ -z "${VIRTUAL_ENV_DISABLE_PROMPT:-}" ] ; then
|
|
||||||
_OLD_VIRTUAL_PS1="${PS1:-}"
|
|
||||||
PS1="(backend) ${PS1:-}"
|
|
||||||
export PS1
|
|
||||||
VIRTUAL_ENV_PROMPT="(backend) "
|
|
||||||
export VIRTUAL_ENV_PROMPT
|
|
||||||
fi
|
|
||||||
|
|
||||||
# This should detect bash and zsh, which have a hash command that must
|
|
||||||
# be called to get it to forget past commands. Without forgetting
|
|
||||||
# past commands the $PATH changes we made may not be respected
|
|
||||||
if [ -n "${BASH:-}" -o -n "${ZSH_VERSION:-}" ] ; then
|
|
||||||
hash -r 2> /dev/null
|
|
||||||
fi
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
@echo off
|
|
||||||
|
|
||||||
rem This file is UTF-8 encoded, so we need to update the current code page while executing it
|
|
||||||
for /f "tokens=2 delims=:." %%a in ('"%SystemRoot%\System32\chcp.com"') do (
|
|
||||||
set _OLD_CODEPAGE=%%a
|
|
||||||
)
|
|
||||||
if defined _OLD_CODEPAGE (
|
|
||||||
"%SystemRoot%\System32\chcp.com" 65001 > nul
|
|
||||||
)
|
|
||||||
|
|
||||||
set VIRTUAL_ENV=C:\project\Kintone\KintoneAppBuilder\backend
|
|
||||||
|
|
||||||
if not defined PROMPT set PROMPT=$P$G
|
|
||||||
|
|
||||||
if defined _OLD_VIRTUAL_PROMPT set PROMPT=%_OLD_VIRTUAL_PROMPT%
|
|
||||||
if defined _OLD_VIRTUAL_PYTHONHOME set PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%
|
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PROMPT=%PROMPT%
|
|
||||||
set PROMPT=(backend) %PROMPT%
|
|
||||||
|
|
||||||
if defined PYTHONHOME set _OLD_VIRTUAL_PYTHONHOME=%PYTHONHOME%
|
|
||||||
set PYTHONHOME=
|
|
||||||
|
|
||||||
if defined _OLD_VIRTUAL_PATH set PATH=%_OLD_VIRTUAL_PATH%
|
|
||||||
if not defined _OLD_VIRTUAL_PATH set _OLD_VIRTUAL_PATH=%PATH%
|
|
||||||
|
|
||||||
set PATH=%VIRTUAL_ENV%\Scripts;%PATH%
|
|
||||||
set VIRTUAL_ENV_PROMPT=(backend)
|
|
||||||
|
|
||||||
:END
|
|
||||||
if defined _OLD_CODEPAGE (
|
|
||||||
"%SystemRoot%\System32\chcp.com" %_OLD_CODEPAGE% > nul
|
|
||||||
set _OLD_CODEPAGE=
|
|
||||||
)
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
@echo off
|
|
||||||
|
|
||||||
if defined _OLD_VIRTUAL_PROMPT (
|
|
||||||
set "PROMPT=%_OLD_VIRTUAL_PROMPT%"
|
|
||||||
)
|
|
||||||
set _OLD_VIRTUAL_PROMPT=
|
|
||||||
|
|
||||||
if defined _OLD_VIRTUAL_PYTHONHOME (
|
|
||||||
set "PYTHONHOME=%_OLD_VIRTUAL_PYTHONHOME%"
|
|
||||||
set _OLD_VIRTUAL_PYTHONHOME=
|
|
||||||
)
|
|
||||||
|
|
||||||
if defined _OLD_VIRTUAL_PATH (
|
|
||||||
set "PATH=%_OLD_VIRTUAL_PATH%"
|
|
||||||
)
|
|
||||||
|
|
||||||
set _OLD_VIRTUAL_PATH=
|
|
||||||
|
|
||||||
set VIRTUAL_ENV=
|
|
||||||
set VIRTUAL_ENV_PROMPT=
|
|
||||||
|
|
||||||
:END
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because one or more lines are too long
@@ -156,10 +156,10 @@ def getsettingfromexcel(df):
|
|||||||
des = df.iloc[2,2]
|
des = df.iloc[2,2]
|
||||||
return {"name":appname,"description":des}
|
return {"name":appname,"description":des}
|
||||||
|
|
||||||
def getsettingfromkintone(app:str,c:config.KINTONE_ENV):
|
def getsettingfromkintone(app:str,env:config.KINTONE_ENV):
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||||
params = {"app":app}
|
params = {"app":app}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/settings.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/app/settings.json"
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
@@ -171,60 +171,101 @@ def analysesettings(excel,kintone):
|
|||||||
updatesettings[key] = excel[key]
|
updatesettings[key] = excel[key]
|
||||||
return updatesettings
|
return updatesettings
|
||||||
|
|
||||||
def createkintoneapp(name:str,c:config.KINTONE_ENV):
|
def createkintoneapp(name:str,env:config.KINTONE_ENV):
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
data = {"name":name}
|
data = {"name":name}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def updateappsettingstokintone(app:str,updates:dict,c:config.KINTONE_ENV):
|
def updateappsettingstokintone(app:str,updates:dict,env:config.KINTONE_ENV):
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/settings.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/settings.json"
|
||||||
data = {"app":app}
|
data = {"app":app}
|
||||||
data.update(updates)
|
data.update(updates)
|
||||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def addfieldstokintone(app:str,fields:dict,c:config.KINTONE_ENV,revision:str = None):
|
def addfieldstokintone(app:str,fields:dict,env:config.KINTONE_ENV,revision:str = None):
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||||
if revision != None:
|
if revision != None:
|
||||||
data = {"app":app,"revision":revision,"properties":fields}
|
data = {"app":app,"revision":revision,"properties":fields}
|
||||||
else:
|
else:
|
||||||
data = {"app":app,"properties":fields}
|
data = {"app":app,"properties":fields}
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
|
r.raise_for_status()
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def updatefieldstokintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
def updatefieldstokintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV):
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||||
data = {"app":app,"properties":fields}
|
data = {"app":app,"properties":fields}
|
||||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def deletefieldsfromkintone(app:str,revision:str,fields:dict,c:config.KINTONE_ENV):
|
def deletefieldsfromkintone(app:str,revision:str,fields:dict,env:config.KINTONE_ENV):
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/form/fields.json"
|
||||||
params = {"app":app,"revision":revision,"fields":fields}
|
params = {"app":app,"revision":revision,"fields":fields}
|
||||||
#r = httpx.delete(url,headers=headers,content=json.dumps(params))
|
#r = httpx.delete(url,headers=headers,content=json.dumps(params))
|
||||||
r = httpx.request(method="DELETE",url=url,headers=headers,content=json.dumps(params))
|
r = httpx.request(method="DELETE",url=url,headers=headers,content=json.dumps(params))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def deoployappfromkintone(app:str,revision:str,c:config.KINTONE_ENV):
|
def deoployappfromkintone(app:str,revision:str,env:config.KINTONE_ENV):
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
||||||
data = {"apps":[{"app":app,"revision":revision}],"revert": False}
|
data = {"apps":[{"app":app,"revision":revision}],"revert": False}
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json
|
return r.json
|
||||||
|
|
||||||
def getfieldsfromkintone(app:str,c:config.KINTONE_ENV):
|
# 既定項目に含めるアプリのフィールドのみ取得する
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
# スペース、枠線、ラベルを含まない
|
||||||
|
def getfieldsfromkintone(app:str,env:config.KINTONE_ENV):
|
||||||
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||||
params = {"app":app}
|
params = {"app":app}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/form/fields.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/app/form/fields.json"
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
|
# フォームに配置するフィールドのみ取得する
|
||||||
|
# スペース、枠線、ラベルも含める
|
||||||
|
def getformfromkintone(app:str,env:config.KINTONE_ENV):
|
||||||
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||||
|
params = {"app":app}
|
||||||
|
url = f"{env.BASE_URL}{config.API_V1_STR}/form.json"
|
||||||
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
|
||||||
|
def merge_kintone_fields(fields_response: dict, form_response: dict) -> dict:
|
||||||
|
fields_properties = fields_response.get('properties', {})
|
||||||
|
form_properties = form_response.get('properties', [])
|
||||||
|
|
||||||
|
merged_properties = {k: v for k, v in fields_properties.items()}
|
||||||
|
|
||||||
|
for index, form_field in enumerate(form_properties):
|
||||||
|
code = form_field.get('code')
|
||||||
|
if code:
|
||||||
|
if code and code not in merged_properties:
|
||||||
|
merged_properties[code] = form_field
|
||||||
|
else:
|
||||||
|
element_id = form_field.get('elementId')
|
||||||
|
if element_id:
|
||||||
|
key = element_id
|
||||||
|
form_field['code']=element_id
|
||||||
|
form_field['label']=form_field.get('type')
|
||||||
|
# else:
|
||||||
|
# key = f"{form_field.get('type')}_{index}"
|
||||||
|
merged_properties[key] = form_field
|
||||||
|
|
||||||
|
merged_response = {
|
||||||
|
'revision': fields_response.get('revision', ''),
|
||||||
|
'properties': merged_properties
|
||||||
|
}
|
||||||
|
|
||||||
|
return merged_response
|
||||||
|
|
||||||
def analysefields(excel,kintone):
|
def analysefields(excel,kintone):
|
||||||
updatefields={}
|
updatefields={}
|
||||||
addfields={}
|
addfields={}
|
||||||
@@ -245,10 +286,10 @@ def analysefields(excel,kintone):
|
|||||||
|
|
||||||
return {"update":updatefields,"add":addfields,"del":delfields}
|
return {"update":updatefields,"add":addfields,"del":delfields}
|
||||||
|
|
||||||
def getprocessfromkintone(app:str,c:config.KINTONE_ENV):
|
def getprocessfromkintone(app:str,env:config.KINTONE_ENV):
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||||
params = {"app":app}
|
params = {"app":app}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/status.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/app/status.json"
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
@@ -333,49 +374,95 @@ def getkintoneorgs(c:config.KINTONE_ENV):
|
|||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def uploadkintonefiles(file,c:config.KINTONE_ENV):
|
def uploadkintonefiles(file,env:config.KINTONE_ENV):
|
||||||
if (file.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
if (file.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
||||||
return {'fileKey':file}
|
return {'fileKey':file}
|
||||||
upload_files = {'file': open(file,'rb')}
|
upload_files = {'file': open(file,'rb')}
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||||
data ={'name':'file','filename':os.path.basename(file)}
|
data ={'name':'file','filename':os.path.basename(file)}
|
||||||
url = f"{c.BASE_URL}/k/v1/file.json"
|
url = f"{env.BASE_URL}/k/v1/file.json"
|
||||||
r = httpx.post(url,headers=headers,data=data,files=upload_files)
|
r = httpx.post(url,headers=headers,data=data,files=upload_files)
|
||||||
|
#{"name":data['filename'],'fileKey':r['fileKey']}
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def updateappjscss(app,uploads,c:config.KINTONE_ENV):
|
def updateappjscss(app,uploads,env:config.KINTONE_ENV):
|
||||||
dsjs = []
|
dsjs = []
|
||||||
dscss = []
|
dscss = []
|
||||||
|
#mobile側
|
||||||
|
mbjs = []
|
||||||
|
mbcss = []
|
||||||
|
customize = getappcustomize(app, env)
|
||||||
|
current_js = customize['desktop'].get('js', [])
|
||||||
|
current_css = customize['desktop'].get('css', [])
|
||||||
|
current_mobile_js = customize['mobile'].get('js', [])
|
||||||
|
current_mobile_css = customize['mobile'].get('css', [])
|
||||||
|
current_js = [item for item in current_js if not (item.get('type') == 'URL' and item.get('url', '').endswith('alc_runtime.js'))]
|
||||||
for upload in uploads:
|
for upload in uploads:
|
||||||
for key in upload:
|
for key in upload:
|
||||||
|
filename = os.path.basename(key)
|
||||||
if key.endswith('.js'):
|
if key.endswith('.js'):
|
||||||
if (key.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
existing_js = next((item for item in current_js
|
||||||
dsjs.append({'type':'URL','url':config.DEPLOY_JS_URL})
|
if item.get('type') == 'FILE' and item['file']['name'] == filename
|
||||||
else:
|
), None)
|
||||||
|
if existing_js:
|
||||||
|
current_js = [item for item in current_js if item.get('type') == 'URL' or item['file'].get('name') != filename]
|
||||||
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||||
|
else:
|
||||||
|
if (key.endswith('alc_runtime.js') and config.DEPLOY_MODE == "DEV"):
|
||||||
|
dsjs.append({'type':'URL','url':config.DEPLOY_JS_URL})
|
||||||
|
else:
|
||||||
|
dsjs.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
||||||
elif key.endswith('.css'):
|
elif key.endswith('.css'):
|
||||||
dscss.append({'type':'FILE','file':{'fileKey':upload[key]}})
|
existing_css = next((item for item in current_css
|
||||||
|
if item.get('type') == 'FILE' and item['file']['name'] == filename
|
||||||
|
), None)
|
||||||
|
if existing_css:
|
||||||
|
current_css = [item for item in current_css if item.get('type') == 'URL' or item['file'].get('name') != filename]
|
||||||
|
dscss.append({'type': 'FILE', 'file': {'fileKey': upload[key]}})
|
||||||
|
else:
|
||||||
|
dscss.append({'type': 'FILE', 'file': {'fileKey': upload[key]}})
|
||||||
|
#現在のJSとCSSがdsjsに追加する
|
||||||
|
dsjs.extend(current_js)
|
||||||
|
dscss.extend(current_css)
|
||||||
|
mbjs.extend(current_mobile_js)
|
||||||
|
mbcss.extend(current_mobile_css)
|
||||||
|
|
||||||
ds ={'js':dsjs,'css':dscss}
|
ds ={'js':dsjs,'css':dscss}
|
||||||
mb ={'js':[],'css':[]}
|
mb ={'js':mbjs,'css':mbcss}
|
||||||
data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb}
|
data = {'app':app,'scope':'ALL','desktop':ds,'mobile':mb,'revision':customize["revision"]}
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
||||||
print(data)
|
print(json.dumps(data))
|
||||||
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
r = httpx.put(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
def createappjs(domainid,app):
|
#kintone カスタマイズ情報
|
||||||
|
def getappcustomize(app,env:config.KINTONE_ENV):
|
||||||
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||||
|
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/customize.json"
|
||||||
|
params = {"app":app}
|
||||||
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
|
return r.json()
|
||||||
|
|
||||||
|
|
||||||
|
def getTempPath(filename):
|
||||||
|
scriptdir = Path(__file__).resolve().parent
|
||||||
|
rootdir = scriptdir.parent.parent.parent.parent
|
||||||
|
fpath = os.path.join(rootdir,"Temp",filename)
|
||||||
|
return fpath
|
||||||
|
|
||||||
|
def createappjs(domain_url,app):
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
flows = get_flows_by_app(db,domainid,app)
|
flows = get_flows_by_app(db,domain_url,app)
|
||||||
db.close()
|
db.close()
|
||||||
content={}
|
content={}
|
||||||
for flow in flows:
|
for flow in flows:
|
||||||
content[flow.eventid] = {'flowid':flow.flowid,'name':flow.name,'content':flow.content}
|
content[flow.eventid] = {'flowid':flow.flowid,'name':flow.name,'content':flow.content}
|
||||||
js = 'const alcflow=' + json.dumps(content)
|
js = 'const alcflow=' + json.dumps(content)
|
||||||
scriptdir = Path(__file__).resolve().parent
|
# scriptdir = Path(__file__).resolve().parent
|
||||||
rootdir = scriptdir.parent.parent.parent.parent
|
# rootdir = scriptdir.parent.parent.parent.parent
|
||||||
fpath = os.path.join(rootdir,"Temp",f"alc_setting_{app}.js")
|
# fpath = os.path.join(rootdir,"Temp",f"alc_setting_{app}.js")
|
||||||
print(rootdir)
|
fpath = getTempPath(f"alc_setting_{app}.js")
|
||||||
print(fpath)
|
print(fpath)
|
||||||
with open(fpath,'w') as file:
|
with open(fpath,'w') as file:
|
||||||
file.write(js)
|
file.write(js)
|
||||||
@@ -434,7 +521,7 @@ async def upload(request:Request,files:t.List[UploadFile] = File(...)):
|
|||||||
return {"files": [file.filename for file in files]}
|
return {"files": [file.filename for file in files]}
|
||||||
|
|
||||||
@r.post("/updatejscss")
|
@r.post("/updatejscss")
|
||||||
async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env = Depends(getkintoneenv)):
|
async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||||
try:
|
try:
|
||||||
jscs=[]
|
jscs=[]
|
||||||
for file in files:
|
for file in files:
|
||||||
@@ -455,66 +542,87 @@ async def jscss(request:Request,app:str,files:t.List[UploadFile] = File(...),env
|
|||||||
raise APIException('kintone:updatejscss',request.url._url, f"Error occurred while update js/css {file.filename} is not an Excel file",e)
|
raise APIException('kintone:updatejscss',request.url._url, f"Error occurred while update js/css {file.filename} is not an Excel file",e)
|
||||||
|
|
||||||
@r.get("/app")
|
@r.get("/app")
|
||||||
async def app(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
async def app(request:Request,app:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||||
try:
|
try:
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/app.json"
|
||||||
params ={"id":app}
|
params ={"id":app}
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('kintone:app',request.url._url, f"Error occurred while get app({c.DOMAIN_NAM}->{app}):",e)
|
raise APIException('kintone:app',request.url._url, f"Error occurred while get app({env.DOMAIN_NAME}->{app}):",e)
|
||||||
|
|
||||||
@r.get("/allapps")
|
@r.get("/allapps")
|
||||||
async def allapps(request:Request,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
async def allapps(request:Request,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||||
try:
|
try:
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/apps.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/apps.json"
|
||||||
r = httpx.get(url,headers=headers)
|
offset = 0
|
||||||
return r.json()
|
limit = 100
|
||||||
|
all_apps = []
|
||||||
|
|
||||||
|
while True:
|
||||||
|
r = httpx.get(f"{url}?limit={limit}&offset={offset}", headers=headers)
|
||||||
|
json_data = r.json()
|
||||||
|
apps = json_data.get("apps",[])
|
||||||
|
all_apps.extend(apps)
|
||||||
|
if len(apps)<limit:
|
||||||
|
break
|
||||||
|
offset += limit
|
||||||
|
return {"apps": all_apps}
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('kintone:allapps',request.url._url, f"Error occurred while get allapps({c.DOMAIN_NAM}):",e)
|
raise APIException('kintone:allapps', request.url._url, f"Error occurred while get allapps({env.DOMAIN_NAME}):", e)
|
||||||
|
|
||||||
@r.get("/appfields")
|
@r.get("/appfields")
|
||||||
async def appfields(request:Request,app:str,env = Depends(getkintoneenv)):
|
async def appfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||||
try:
|
try:
|
||||||
return getfieldsfromkintone(app,env)
|
return getfieldsfromkintone(app,env)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAM}->{app}):",e)
|
raise APIException('kintone:appfields',request.url._url, f"Error occurred while get app fileds({env.DOMAIN_NAME}->{app}):",e)
|
||||||
|
|
||||||
|
@r.get("/allfields")
|
||||||
|
async def allfields(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||||
|
try:
|
||||||
|
field_resp = getfieldsfromkintone(app,env)
|
||||||
|
form_resp = getformfromkintone(app,env)
|
||||||
|
return merge_kintone_fields(field_resp,form_resp)
|
||||||
|
except Exception as e:
|
||||||
|
raise APIException('kintone:allfields',request.url._url, f"Error occurred while get form fileds({env.DOMAIN_NAME}->{app}):",e)
|
||||||
|
|
||||||
@r.get("/appprocess")
|
@r.get("/appprocess")
|
||||||
async def appprocess(request:Request,app:str,env = Depends(getkintoneenv)):
|
async def appprocess(request:Request,app:str,env:config.KINTONE_ENV = Depends(getkintoneenv)):
|
||||||
try:
|
try:
|
||||||
return getprocessfromkintone(app,env)
|
return getprocessfromkintone(app,env)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAM}->{app}):",e)
|
raise APIException('kintone:appprocess',request.url._url, f"Error occurred while get app process({env.DOMAIN_NAME}->{app}):",e)
|
||||||
|
|
||||||
@r.get("/alljscss")
|
@r.get("/alljscss")
|
||||||
async def alljscs(request:Request,app:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
async def alljscs(request:Request,app:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||||
try:
|
try:
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/app/customize.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/app/customize.json"
|
||||||
params = {"app":app}
|
params = {"app":app}
|
||||||
r = httpx.get(url,headers=headers,params=params)
|
r = httpx.get(url,headers=headers,params=params)
|
||||||
return r.json()
|
return r.json()
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({c.DOMAIN_NAM}->{app}):",e)
|
raise APIException('kintone:alljscss',request.url._url, f"Error occurred while get app js/css({env.DOMAIN_NAME}->{app}):",e)
|
||||||
|
|
||||||
@r.post("/createapp",)
|
@r.post("/createapp",)
|
||||||
async def createapp(request:Request,name:str,c:config.KINTONE_ENV=Depends(getkintoneenv)):
|
async def createapp(request:Request,name:str,env:config.KINTONE_ENV=Depends(getkintoneenv)):
|
||||||
try:
|
try:
|
||||||
headers={config.API_V1_AUTH_KEY:c.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
headers={config.API_V1_AUTH_KEY:env.API_V1_AUTH_VALUE,"Content-Type": "application/json"}
|
||||||
data = {"name":name}
|
data = {"name":name}
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app.json"
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
result = r.json()
|
result = r.json()
|
||||||
if result.get("app") != None:
|
if result.get("app") != None:
|
||||||
url = f"{c.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
url = f"{env.BASE_URL}{config.API_V1_STR}/preview/app/deploy.json"
|
||||||
data = {"apps":[result],"revert": False}
|
data = {"apps":[result],"revert": False}
|
||||||
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
r = httpx.post(url,headers=headers,data=json.dumps(data))
|
||||||
return r.json
|
return r.json
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({c.DOMAIN_NAM}->{name}):",e)
|
raise APIException('kintone:createapp',request.url._url, f"Error occurred while create app({env.DOMAIN_NAME}->{name}):",e)
|
||||||
|
|
||||||
|
|
||||||
@r.post("/createappfromexcel",)
|
@r.post("/createappfromexcel",)
|
||||||
@@ -643,7 +751,7 @@ async def updateprocessfromexcel(request:Request,app:str,env = Depends(getkinton
|
|||||||
if deploy:
|
if deploy:
|
||||||
result = deoployappfromkintone(app,revision,env)
|
result = deoployappfromkintone(app,revision,env)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('kintone:updateprocessfromexcel',request.url._url, f"Error occurred while update process ({env.DOMAIN_NAM}->{app}):",e)
|
raise APIException('kintone:updateprocessfromexcel',request.url._url, f"Error occurred while update process ({env.DOMAIN_NAME}->{app}):",e)
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|
||||||
@@ -653,15 +761,17 @@ async def createjstokintone(request:Request,app:str,env:config.KINTONE_ENV = Dep
|
|||||||
try:
|
try:
|
||||||
jscs=[]
|
jscs=[]
|
||||||
files=[]
|
files=[]
|
||||||
files.append(createappjs(env.DOMAIN_ID, app))
|
files.append(createappjs(env.BASE_URL, app))
|
||||||
files.append('Temp\\alc_runtime.js')
|
files.append(getTempPath('alc_runtime.js'))
|
||||||
|
files.append(getTempPath('alc_runtime.css'))
|
||||||
for file in files:
|
for file in files:
|
||||||
upload = uploadkintonefiles(file,env)
|
upload = uploadkintonefiles(file,env)
|
||||||
if upload.get('fileKey') != None:
|
if upload.get('fileKey') != None:
|
||||||
|
print(upload)
|
||||||
jscs.append({ file :upload['fileKey']})
|
jscs.append({ file :upload['fileKey']})
|
||||||
appjscs = updateappjscss(app,jscs,env)
|
appjscs = updateappjscss(app,jscs,env)
|
||||||
if appjscs.get("revision") != None:
|
if appjscs.get("revision") != None:
|
||||||
deoployappfromkintone(app,appjscs["revision"],env)
|
deoployappfromkintone(app,appjscs["revision"],env)
|
||||||
return appjscs
|
return appjscs
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('kintone:createjstokintone',request.url._url, f"Error occurred while create js ({env.DOMAIN_NAM}->{app}):",e)
|
raise APIException('kintone:createjstokintone',request.url._url, f"Error occurred while create js ({env.DOMAIN_NAME}->{app}):",e)
|
||||||
|
|||||||
@@ -1,14 +1,74 @@
|
|||||||
from fastapi import Request,Depends, APIRouter, UploadFile,HTTPException,File
|
from http import HTTPStatus
|
||||||
|
from fastapi import Query, Request,Depends, APIRouter, UploadFile,HTTPException,File
|
||||||
|
from fastapi.responses import JSONResponse
|
||||||
|
# from app.core.operation import log_operation
|
||||||
from app.db import Base,engine
|
from app.db import Base,engine
|
||||||
from app.db.session import get_db
|
from app.db.session import get_db
|
||||||
from app.db.crud import *
|
from app.db.crud import *
|
||||||
from app.db.schemas import *
|
from app.db.schemas import *
|
||||||
from typing import List
|
from typing import List, Optional
|
||||||
from app.core.auth import get_current_active_user,get_current_user
|
from app.core.auth import get_current_active_user,get_current_user
|
||||||
from app.core.apiexception import APIException
|
from app.core.apiexception import APIException
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
import app.core.config as config
|
||||||
|
|
||||||
platform_router = r = APIRouter()
|
platform_router = r = APIRouter()
|
||||||
|
|
||||||
|
|
||||||
|
@r.get(
|
||||||
|
"/apps",
|
||||||
|
response_model=List[AppList],
|
||||||
|
response_model_exclude_none=True,
|
||||||
|
)
|
||||||
|
async def apps_list(
|
||||||
|
request: Request,
|
||||||
|
user = Depends(get_current_user),
|
||||||
|
db=Depends(get_db),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
|
||||||
|
domain = get_activedomain(db, user.id)
|
||||||
|
platformapps = get_apps(db,domain.url)
|
||||||
|
kintoneevn = config.KINTONE_ENV(domain)
|
||||||
|
headers={config.API_V1_AUTH_KEY:kintoneevn.API_V1_AUTH_VALUE}
|
||||||
|
url = f"{kintoneevn.BASE_URL}{config.API_V1_STR}/apps.json"
|
||||||
|
offset = 0
|
||||||
|
limit = 100
|
||||||
|
all_apps = []
|
||||||
|
|
||||||
|
while True:
|
||||||
|
r = httpx.get(f"{url}?limit={limit}&offset={offset}", headers=headers)
|
||||||
|
json_data = r.json()
|
||||||
|
apps = json_data.get("apps",[])
|
||||||
|
all_apps.extend(apps)
|
||||||
|
if len(apps)<limit:
|
||||||
|
break
|
||||||
|
offset += limit
|
||||||
|
|
||||||
|
kintone_apps_dict = {app['appId']: app for app in all_apps}
|
||||||
|
filtered_apps = []
|
||||||
|
for papp in platformapps:
|
||||||
|
if papp.appid in kintone_apps_dict:
|
||||||
|
papp.appname = kintone_apps_dict[papp.appid]["name"]
|
||||||
|
filtered_apps.append(papp)
|
||||||
|
return filtered_apps
|
||||||
|
except Exception as e:
|
||||||
|
raise APIException('platform:apps',request.url._url,f"Error occurred while get apps:",e)
|
||||||
|
|
||||||
|
@r.post("/apps", response_model=AppList, response_model_exclude_none=True)
|
||||||
|
async def apps_update(
|
||||||
|
request: Request,
|
||||||
|
app: AppVersion,
|
||||||
|
user=Depends(get_current_user),
|
||||||
|
db=Depends(get_db),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return update_appversion(db, app,user.id)
|
||||||
|
except Exception as e:
|
||||||
|
raise APIException('platform:apps',request.url._url,f"Error occurred while get create app :",e)
|
||||||
|
|
||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/appsettings/{id}",
|
"/appsettings/{id}",
|
||||||
response_model=App,
|
response_model=App,
|
||||||
@@ -129,7 +189,7 @@ async def flow_list(
|
|||||||
try:
|
try:
|
||||||
domain = get_activedomain(db, user.id)
|
domain = get_activedomain(db, user.id)
|
||||||
print("domain=>",domain)
|
print("domain=>",domain)
|
||||||
flows = get_flows_by_app(db, domain.id, appid)
|
flows = get_flows_by_app(db, domain.url, appid)
|
||||||
return flows
|
return flows
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by appid:",e)
|
raise APIException('platform:flow',request.url._url,f"Error occurred while get flow by appid:",e)
|
||||||
@@ -144,7 +204,7 @@ async def flow_create(
|
|||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
domain = get_activedomain(db, user.id)
|
domain = get_activedomain(db, user.id)
|
||||||
return create_flow(db, domain.id, flow)
|
return create_flow(db, domain.url, flow)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while create flow:",e)
|
raise APIException('platform:flow',request.url._url,f"Error occurred while create flow:",e)
|
||||||
|
|
||||||
@@ -154,11 +214,14 @@ async def flow_create(
|
|||||||
)
|
)
|
||||||
async def flow_edit(
|
async def flow_edit(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
flowid: str,
|
||||||
flow: FlowBase,
|
flow: FlowBase,
|
||||||
|
user=Depends(get_current_user),
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
return edit_flow(db, flow)
|
domain = get_activedomain(db, user.id)
|
||||||
|
return edit_flow(db,domain.url, flow)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",e)
|
raise APIException('platform:flow',request.url._url,f"Error occurred while edit flow:",e)
|
||||||
|
|
||||||
@@ -196,10 +259,11 @@ async def domain_details(
|
|||||||
async def domain_create(
|
async def domain_create(
|
||||||
request: Request,
|
request: Request,
|
||||||
domain: DomainBase,
|
domain: DomainBase,
|
||||||
|
user=Depends(get_current_user),
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
return create_domain(db, domain)
|
return create_domain(db, domain,user.id)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e)
|
raise APIException('platform:domain',request.url._url,f"Error occurred while create domain:",e)
|
||||||
|
|
||||||
@@ -233,16 +297,17 @@ async def domain_delete(
|
|||||||
|
|
||||||
@r.get(
|
@r.get(
|
||||||
"/domain",
|
"/domain",
|
||||||
response_model=List[Domain],
|
# response_model=List[Domain],
|
||||||
response_model_exclude_none=True,
|
response_model_exclude_none=True,
|
||||||
)
|
)
|
||||||
async def userdomain_details(
|
async def userdomain_details(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
userId: Optional[int] = Query(None, alias="userId"),
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
domains = get_domain(db, user.id)
|
domains = get_domain(db, userId if userId is not None else user.id)
|
||||||
return domains
|
return domains
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:domain',request.url._url,f"Error occurred while get user({user.id}) domain:",e)
|
raise APIException('platform:domain',request.url._url,f"Error occurred while get user({user.id}) domain:",e)
|
||||||
@@ -254,7 +319,7 @@ async def userdomain_details(
|
|||||||
async def create_userdomain(
|
async def create_userdomain(
|
||||||
request: Request,
|
request: Request,
|
||||||
userid: int,
|
userid: int,
|
||||||
domainids:list,
|
domainids:List[int] ,
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
@@ -285,11 +350,15 @@ async def userdomain_delete(
|
|||||||
)
|
)
|
||||||
async def get_useractivedomain(
|
async def get_useractivedomain(
|
||||||
request: Request,
|
request: Request,
|
||||||
|
userId: Optional[int] = Query(None, alias="userId"),
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
domain = get_activedomain(db, user.id)
|
# domain = get_activedomain(db, user.id)
|
||||||
|
domain = get_activedomain(db, userId if userId is not None else user.id)
|
||||||
|
if domain is None:
|
||||||
|
return JSONResponse(content=None,status_code=HTTPStatus.OK)
|
||||||
return domain
|
return domain
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:activedomain',request.url._url,f"Error occurred while get user({user.id}) activedomain:",e)
|
raise APIException('platform:activedomain',request.url._url,f"Error occurred while get user({user.id}) activedomain:",e)
|
||||||
@@ -301,11 +370,12 @@ async def get_useractivedomain(
|
|||||||
async def update_activeuserdomain(
|
async def update_activeuserdomain(
|
||||||
request: Request,
|
request: Request,
|
||||||
domainid:int,
|
domainid:int,
|
||||||
|
userId: Optional[int] = Query(None, alias="userId"),
|
||||||
user=Depends(get_current_user),
|
user=Depends(get_current_user),
|
||||||
db=Depends(get_db),
|
db=Depends(get_db),
|
||||||
):
|
):
|
||||||
try:
|
try:
|
||||||
domain = active_userdomain(db, user.id,domainid)
|
domain = active_userdomain(db, userId if userId is not None else user.id,domainid)
|
||||||
return domain
|
return domain
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
raise APIException('platform:activedomain',request.url._url,f"Error occurred while update user({user.id}) activedomain:",e)
|
raise APIException('platform:activedomain',request.url._url,f"Error occurred while update user({user.id}) activedomain:",e)
|
||||||
|
|||||||
@@ -1,22 +1,35 @@
|
|||||||
from fastapi import HTTPException, status
|
from fastapi import HTTPException, status
|
||||||
|
import httpx
|
||||||
from app.db.schemas import ErrorCreate
|
from app.db.schemas import ErrorCreate
|
||||||
from app.db.session import SessionLocal
|
from app.db.session import SessionLocal
|
||||||
from app.db.crud import create_log
|
from app.db.crud import create_log
|
||||||
|
|
||||||
class APIException(Exception):
|
class APIException(Exception):
|
||||||
|
def __init__(self, location: str, title: str, content: str, e: Exception):
|
||||||
def __init__(self,location:str,title:str,content:str,e:Exception):
|
self.detail = str(e)
|
||||||
if(str(e) == ''):
|
self.status_code = 500
|
||||||
content += e.detail
|
if isinstance(e,httpx.HTTPStatusError):
|
||||||
self.detail = e.detail
|
try:
|
||||||
self.status_code = e.status_code
|
error_response = e.response.json()
|
||||||
else:
|
self.detail = error_response.get('message', self.detail)
|
||||||
self.detail = str(e)
|
self.status_code = e.response.status_code
|
||||||
content += str(e)
|
content += self.detail
|
||||||
self.status_code = 500
|
except ValueError:
|
||||||
if(len(content) > 5000):
|
pass
|
||||||
content =content[0:5000]
|
elif hasattr(e, 'detail'):
|
||||||
self.error = ErrorCreate(location=location,title=title,content=content)
|
self.detail = e.detail
|
||||||
|
self.status_code = e.status_code if hasattr(e, 'status_code') else 500
|
||||||
|
content += e.detail
|
||||||
|
else:
|
||||||
|
self.detail = str(e)
|
||||||
|
self.status_code = 500
|
||||||
|
content += str(e)
|
||||||
|
|
||||||
|
if len(content) > 5000:
|
||||||
|
content = content[:5000]
|
||||||
|
|
||||||
|
self.error = ErrorCreate(location=location, title=title, content=content)
|
||||||
|
super().__init__(self.error)
|
||||||
|
|
||||||
def writedblog(exc: APIException):
|
def writedblog(exc: APIException):
|
||||||
db = SessionLocal()
|
db = SessionLocal()
|
||||||
|
|||||||
@@ -1,26 +1,29 @@
|
|||||||
import os
|
import os
|
||||||
import base64
|
import base64
|
||||||
|
|
||||||
|
|
||||||
PROJECT_NAME = "KintoneAppBuilder"
|
PROJECT_NAME = "KintoneAppBuilder"
|
||||||
|
|
||||||
#SQLALCHEMY_DATABASE_URI = "postgres://maxz64:m@xz1205@alicornkintone.postgres.database.azure.com/postgres"
|
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/dev"
|
||||||
SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/postgres"
|
SQLALCHEMY_DATABASE_URI = f"postgresql+psycopg2://kabAdmin:P%40ssw0rd!@kintonetooldb.postgres.database.azure.com/dev_v2"
|
||||||
|
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@kintonetooldb.postgres.database.azure.com/test"
|
||||||
|
#SQLALCHEMY_DATABASE_URI = "postgres://kabAdmin:P@ssw0rd!@ktune-prod-db.postgres.database.azure.com/postgres"
|
||||||
API_V1_STR = "/k/v1"
|
API_V1_STR = "/k/v1"
|
||||||
|
|
||||||
API_V1_AUTH_KEY = "X-Cybozu-Authorization"
|
API_V1_AUTH_KEY = "X-Cybozu-Authorization"
|
||||||
|
|
||||||
DEPLOY_MODE = "DEV" #DEV,PROD
|
DEPLOY_MODE = "PROD" #DEV,PROD
|
||||||
|
|
||||||
#DEPLOY_JS_URL = "https://ka-addin.azurewebsites.net/alc_runtime.js"
|
DEPLOY_JS_URL = "https://ka-addin.azurewebsites.net/alc_runtime.js"
|
||||||
DEPLOY_JS_URL = "https://92dc-133-139-109-57.ngrok-free.app/alc_runtime.js"
|
|
||||||
|
|
||||||
KINTONE_FIELD_TYPE=["GROUP","GROUP_SELECT","CHECK_BOX","SUBTABLE","DROP_DOWN","USER_SELECT","RADIO_BUTTON","RICH_TEXT","LINK","REFERENCE_TABLE","CALC","TIME","NUMBER","ORGANIZATION_SELECT","FILE","DATETIME","DATE","MULTI_SELECT","SINGLE_LINE_TEXT","MULTI_LINE_TEXT"]
|
KINTONE_FIELD_TYPE=["GROUP","GROUP_SELECT","CHECK_BOX","SUBTABLE","DROP_DOWN","USER_SELECT","RADIO_BUTTON","RICH_TEXT","LINK","REFERENCE_TABLE","CALC","TIME","NUMBER","ORGANIZATION_SELECT","FILE","DATETIME","DATE","MULTI_SELECT","SINGLE_LINE_TEXT","MULTI_LINE_TEXT"]
|
||||||
|
|
||||||
KINTONE_FIELD_PROPERTY=['label','code','type','required','unique','maxValue','minValue','maxLength','minLength','defaultValue','defaultNowValue','options','expression','hideExpression','digit','protocol','displayScale','unit','unitPosition']
|
KINTONE_FIELD_PROPERTY=['label','code','type','required','unique','maxValue','minValue','maxLength','minLength','defaultValue','defaultNowValue','options','expression','hideExpression','digit','protocol','displayScale','unit','unitPosition']
|
||||||
|
|
||||||
|
KINTONE_PSW_CRYPTO_KEY=bytes.fromhex("53 6c 93 bd 48 ad b5 c0 93 df a1 27 25 a1 a3 32 a2 03 3b a0 27 1f 51 dc 20 0e 6c d7 be fc fb ea")
|
||||||
|
|
||||||
class KINTONE_ENV:
|
class KINTONE_ENV:
|
||||||
|
|
||||||
BASE_URL = ""
|
BASE_URL = ""
|
||||||
|
|
||||||
API_V1_AUTH_VALUE = ""
|
API_V1_AUTH_VALUE = ""
|
||||||
@@ -36,4 +39,4 @@ class KINTONE_ENV:
|
|||||||
self.DOMAIN_ID=domain.id
|
self.DOMAIN_ID=domain.id
|
||||||
self.BASE_URL = domain.url
|
self.BASE_URL = domain.url
|
||||||
self.KINTONE_USER = domain.kintoneuser
|
self.KINTONE_USER = domain.kintoneuser
|
||||||
self.API_V1_AUTH_VALUE = base64.b64encode(bytes(f"{domain.kintoneuser}:{domain.kintonepwd}","utf-8"))
|
self.API_V1_AUTH_VALUE = base64.b64encode(bytes(f"{domain.kintoneuser}:{domain.decrypt_kintonepwd()}","utf-8"))
|
||||||
@@ -2,6 +2,10 @@ import jwt
|
|||||||
from fastapi.security import OAuth2PasswordBearer
|
from fastapi.security import OAuth2PasswordBearer
|
||||||
from passlib.context import CryptContext
|
from passlib.context import CryptContext
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from cryptography.hazmat.primitives.ciphers import Cipher, algorithms
|
||||||
|
import os
|
||||||
|
import base64
|
||||||
|
from app.core import config
|
||||||
|
|
||||||
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token")
|
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="/api/token")
|
||||||
|
|
||||||
@@ -29,3 +33,32 @@ def create_access_token(*, data: dict, expires_delta: timedelta = None):
|
|||||||
to_encode.update({"exp": expire})
|
to_encode.update({"exp": expire})
|
||||||
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
encoded_jwt = jwt.encode(to_encode, SECRET_KEY, algorithm=ALGORITHM)
|
||||||
return encoded_jwt
|
return encoded_jwt
|
||||||
|
|
||||||
|
def chacha20Encrypt(plaintext:str, key=config.KINTONE_PSW_CRYPTO_KEY):
|
||||||
|
if plaintext is None or plaintext == '':
|
||||||
|
return None
|
||||||
|
nonce = os.urandom(16)
|
||||||
|
algorithm = algorithms.ChaCha20(key, nonce)
|
||||||
|
cipher = Cipher(algorithm, mode=None)
|
||||||
|
encryptor = cipher.encryptor()
|
||||||
|
ciphertext = encryptor.update(plaintext.encode('utf-8')) + encryptor.finalize()
|
||||||
|
return base64.b64encode(nonce +'𒀸'.encode('utf-8')+ ciphertext).decode('utf-8')
|
||||||
|
|
||||||
|
def chacha20Decrypt(encoded_str:str, key=config.KINTONE_PSW_CRYPTO_KEY):
|
||||||
|
try:
|
||||||
|
decoded_data = base64.b64decode(encoded_str)
|
||||||
|
if len(decoded_data) < 18:
|
||||||
|
return encoded_str
|
||||||
|
special_char = decoded_data[16:20]
|
||||||
|
if special_char != '𒀸'.encode('utf-8'):
|
||||||
|
return encoded_str
|
||||||
|
nonce = decoded_data[:16]
|
||||||
|
ciphertext = decoded_data[20:]
|
||||||
|
except Exception as e:
|
||||||
|
print(f"An error occurred: {e}")
|
||||||
|
return encoded_str
|
||||||
|
algorithm = algorithms.ChaCha20(key, nonce)
|
||||||
|
cipher = Cipher(algorithm, mode=None)
|
||||||
|
decryptor = cipher.decryptor()
|
||||||
|
plaintext_bytes = decryptor.update(ciphertext) + decryptor.finalize()
|
||||||
|
return plaintext_bytes.decode('utf-8')
|
||||||
@@ -4,7 +4,7 @@ from sqlalchemy import and_
|
|||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
from . import models, schemas
|
from . import models, schemas
|
||||||
from app.core.security import get_password_hash
|
from app.core.security import chacha20Decrypt, get_password_hash
|
||||||
|
|
||||||
|
|
||||||
def get_user(db: Session, user_id: int):
|
def get_user(db: Session, user_id: int):
|
||||||
@@ -69,6 +69,47 @@ def edit_user(
|
|||||||
db.refresh(db_user)
|
db.refresh(db_user)
|
||||||
return db_user
|
return db_user
|
||||||
|
|
||||||
|
def get_apps(
|
||||||
|
db: Session,
|
||||||
|
domain_url:str
|
||||||
|
) -> t.List[schemas.AppList]:
|
||||||
|
return db.query(models.App).filter(models.App.domainurl == domain_url).all()
|
||||||
|
|
||||||
|
def update_appversion(db: Session, appedit: schemas.AppVersion,userid:int):
|
||||||
|
app = db.query(models.App).filter(and_(models.App.domainurl == appedit.domainurl,models.App.appid == appedit.appid)).first()
|
||||||
|
if app:
|
||||||
|
app.version = app.version + 1
|
||||||
|
db_app = app
|
||||||
|
appver = app.version
|
||||||
|
else:
|
||||||
|
appver = 1
|
||||||
|
db_app = models.App(
|
||||||
|
domainurl = appedit.domainurl,
|
||||||
|
appid=appedit.appid,
|
||||||
|
appname=appedit.appname,
|
||||||
|
version = 1,
|
||||||
|
updateuser= userid
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(db_app)
|
||||||
|
|
||||||
|
flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == appedit.domainurl,models.App.appid == appedit.appid))
|
||||||
|
for flow in flows:
|
||||||
|
db_flowhistory = models.FlowHistory(
|
||||||
|
flowid = flow.flowid,
|
||||||
|
appid = flow.appid,
|
||||||
|
eventid = flow.eventid,
|
||||||
|
domainurl = flow.domainurl,
|
||||||
|
name = flow.name,
|
||||||
|
content = flow.content,
|
||||||
|
createuser = userid,
|
||||||
|
version = appver
|
||||||
|
)
|
||||||
|
db.add(db_flowhistory)
|
||||||
|
|
||||||
|
db.commit()
|
||||||
|
db.refresh(db_app)
|
||||||
|
return db_app
|
||||||
|
|
||||||
def get_appsetting(db: Session, id: int):
|
def get_appsetting(db: Session, id: int):
|
||||||
app = db.query(models.AppSetting).get(id)
|
app = db.query(models.AppSetting).get(id)
|
||||||
@@ -125,12 +166,12 @@ def get_actions(db: Session):
|
|||||||
return actions
|
return actions
|
||||||
|
|
||||||
|
|
||||||
def create_flow(db: Session, domainid: int, flow: schemas.FlowBase):
|
def create_flow(db: Session, domainurl: str, flow: schemas.FlowBase):
|
||||||
db_flow = models.Flow(
|
db_flow = models.Flow(
|
||||||
flowid=flow.flowid,
|
flowid=flow.flowid,
|
||||||
appid=flow.appid,
|
appid=flow.appid,
|
||||||
eventid=flow.eventid,
|
eventid=flow.eventid,
|
||||||
domainid=domainid,
|
domainurl=domainurl,
|
||||||
name=flow.name,
|
name=flow.name,
|
||||||
content=flow.content
|
content=flow.content
|
||||||
)
|
)
|
||||||
@@ -149,15 +190,17 @@ def delete_flow(db: Session, flowid: str):
|
|||||||
|
|
||||||
|
|
||||||
def edit_flow(
|
def edit_flow(
|
||||||
db: Session, flow: schemas.FlowBase
|
db: Session, domainurl: str, flow: schemas.FlowBase
|
||||||
) -> schemas.Flow:
|
) -> schemas.Flow:
|
||||||
db_flow = get_flow(db, flow.flowid)
|
db_flow = get_flow(db, flow.flowid)
|
||||||
if not db_flow:
|
if not db_flow:
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Flow not found")
|
#見つからない時新規作成
|
||||||
|
return create_flow(db,domainurl,flow)
|
||||||
|
|
||||||
update_data = flow.dict(exclude_unset=True)
|
update_data = flow.dict(exclude_unset=True)
|
||||||
|
|
||||||
for key, value in update_data.items():
|
for key, value in update_data.items():
|
||||||
setattr(db_flow, key, value)
|
setattr(db_flow, key, value)
|
||||||
|
|
||||||
db.add(db_flow)
|
db.add(db_flow)
|
||||||
db.commit()
|
db.commit()
|
||||||
@@ -173,17 +216,18 @@ def get_flows(db: Session, flowid: str):
|
|||||||
|
|
||||||
def get_flow(db: Session, flowid: str):
|
def get_flow(db: Session, flowid: str):
|
||||||
flow = db.query(models.Flow).filter(models.Flow.flowid == flowid).first()
|
flow = db.query(models.Flow).filter(models.Flow.flowid == flowid).first()
|
||||||
if not flow:
|
# if not flow:
|
||||||
raise HTTPException(status_code=404, detail="Data not found")
|
# raise HTTPException(status_code=404, detail="Data not found")
|
||||||
return flow
|
return flow
|
||||||
|
|
||||||
def get_flows_by_app(db: Session, domainid: int, appid: str):
|
def get_flows_by_app(db: Session,domainurl: str, appid: str):
|
||||||
flows = db.query(models.Flow).filter(and_(models.Flow.domainid == domainid,models.Flow.appid == appid)).all()
|
flows = db.query(models.Flow).filter(and_(models.Flow.domainurl == domainurl,models.Flow.appid == appid)).all()
|
||||||
if not flows:
|
if not flows:
|
||||||
raise Exception("Data not found")
|
raise Exception("Data not found")
|
||||||
return flows
|
return flows
|
||||||
|
|
||||||
def create_domain(db: Session, domain: schemas.DomainBase):
|
def create_domain(db: Session, domain: schemas.DomainBase,userid:int):
|
||||||
|
domain.encrypt_kintonepwd()
|
||||||
db_domain = models.Domain(
|
db_domain = models.Domain(
|
||||||
tenantid = domain.tenantid,
|
tenantid = domain.tenantid,
|
||||||
name=domain.name,
|
name=domain.name,
|
||||||
@@ -192,6 +236,8 @@ def create_domain(db: Session, domain: schemas.DomainBase):
|
|||||||
kintonepwd=domain.kintonepwd
|
kintonepwd=domain.kintonepwd
|
||||||
)
|
)
|
||||||
db.add(db_domain)
|
db.add(db_domain)
|
||||||
|
db.flush()
|
||||||
|
add_userdomain(db,userid,db_domain.id)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(db_domain)
|
db.refresh(db_domain)
|
||||||
return db_domain
|
return db_domain
|
||||||
@@ -208,30 +254,32 @@ def delete_domain(db: Session,id: int):
|
|||||||
def edit_domain(
|
def edit_domain(
|
||||||
db: Session, domain: schemas.DomainBase
|
db: Session, domain: schemas.DomainBase
|
||||||
) -> schemas.Domain:
|
) -> schemas.Domain:
|
||||||
|
domain.encrypt_kintonepwd()
|
||||||
db_domain = db.query(models.Domain).get(domain.id)
|
db_domain = db.query(models.Domain).get(domain.id)
|
||||||
if not db_domain:
|
if not db_domain:
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||||
update_data = domain.dict(exclude_unset=True)
|
update_data = domain.dict(exclude_unset=True)
|
||||||
|
|
||||||
for key, value in update_data.items():
|
for key, value in update_data.items():
|
||||||
if(key != "id"):
|
if key != "id" and not (key == "kintonepwd" and (value is None or value == "")):
|
||||||
setattr(db_domain, key, value)
|
setattr(db_domain, key, value)
|
||||||
|
print(str(db_domain))
|
||||||
db.add(db_domain)
|
db.add(db_domain)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(db_domain)
|
db.refresh(db_domain)
|
||||||
return db_domain
|
return db_domain
|
||||||
|
|
||||||
def add_userdomain(db: Session, userid:int,domainids:list):
|
|
||||||
for domainid in domainids:
|
def add_userdomain(db: Session, userid:int,domainid:int):
|
||||||
db_domain = models.UserDomain(
|
user_domain = models.UserDomain(userid = userid, domainid = domainid )
|
||||||
userid = userid,
|
db.add(user_domain)
|
||||||
domainid = domainid
|
return user_domain
|
||||||
)
|
|
||||||
db.add(db_domain)
|
def add_userdomains(db: Session, userid:int,domainids:list[str]):
|
||||||
|
dbCommits = list(map(lambda domainid: models.UserDomain(userid = userid, domainid = domainid ), domainids))
|
||||||
|
db.bulk_save_objects(dbCommits)
|
||||||
db.commit()
|
db.commit()
|
||||||
db.refresh(db_domain)
|
return dbCommits
|
||||||
return db_domain
|
|
||||||
|
|
||||||
def delete_userdomain(db: Session, userid: int,domainid: int):
|
def delete_userdomain(db: Session, userid: int,domainid: int):
|
||||||
db_domain = db.query(models.UserDomain).filter(and_(models.UserDomain.userid == userid,models.UserDomain.domainid == domainid)).first()
|
db_domain = db.query(models.UserDomain).filter(and_(models.UserDomain.userid == userid,models.UserDomain.domainid == domainid)).first()
|
||||||
@@ -254,22 +302,35 @@ def active_userdomain(db: Session, userid: int,domainid: int):
|
|||||||
db.commit()
|
db.commit()
|
||||||
return db_userdomains
|
return db_userdomains
|
||||||
|
|
||||||
def get_activedomain(db: Session, userid: int):
|
def get_activedomain(db: Session, userid: int)-> t.Optional[models.Domain]:
|
||||||
db_domain = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(and_(models.UserDomain.userid == userid,models.UserDomain.active == True)).first()
|
user_domains = (db.query(models.Domain,models.UserDomain.active)
|
||||||
if not db_domain:
|
.join(models.UserDomain,models.UserDomain.domainid == models.Domain.id )
|
||||||
raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
.filter(models.UserDomain.userid == userid)
|
||||||
|
.all())
|
||||||
|
db_domain=None
|
||||||
|
if len(user_domains)==1:
|
||||||
|
db_domain = user_domains[0][0];
|
||||||
|
else:
|
||||||
|
db_domain = next((domain for domain,active in user_domains if active),None)
|
||||||
|
# raise HTTPException(status.HTTP_404_NOT_FOUND, detail="Domain not found")
|
||||||
return db_domain
|
return db_domain
|
||||||
|
|
||||||
def get_domain(db: Session, userid: str):
|
def get_domain(db: Session, userid: str):
|
||||||
domains = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(models.UserDomain.userid == userid).all()
|
domains = db.query(models.Domain).join(models.UserDomain,models.UserDomain.domainid == models.Domain.id ).filter(models.UserDomain.userid == userid).all()
|
||||||
if not domains:
|
# if not domains:
|
||||||
raise HTTPException(status_code=404, detail="Data not found")
|
# raise HTTPException(status_code=404, detail="Data not found")
|
||||||
|
# for domain in domains:
|
||||||
|
# decrypted_pwd = chacha20Decrypt(domain.kintonepwd)
|
||||||
|
# domain.kintonepwd = decrypted_pwd
|
||||||
return domains
|
return domains
|
||||||
|
|
||||||
def get_domains(db: Session,tenantid:str):
|
def get_domains(db: Session,tenantid:str):
|
||||||
domains = db.query(models.Domain).filter(models.Domain.tenantid == tenantid ).all()
|
domains = db.query(models.Domain).filter(models.Domain.tenantid == tenantid ).all()
|
||||||
if not domains:
|
if not domains:
|
||||||
raise HTTPException(status_code=404, detail="Data not found")
|
raise HTTPException(status_code=404, detail="Data not found")
|
||||||
|
# for domain in domains:
|
||||||
|
# decrypted_pwd = chacha20Decrypt(domain.kintonepwd)
|
||||||
|
# domain.kintonepwd = decrypted_pwd
|
||||||
return domains
|
return domains
|
||||||
|
|
||||||
def get_events(db: Session):
|
def get_events(db: Session):
|
||||||
@@ -278,9 +339,35 @@ def get_events(db: Session):
|
|||||||
raise HTTPException(status_code=404, detail="Data not found")
|
raise HTTPException(status_code=404, detail="Data not found")
|
||||||
return events
|
return events
|
||||||
|
|
||||||
|
def get_category(db:Session):
|
||||||
|
categorys=db.query(models.Category).all()
|
||||||
|
return categorys
|
||||||
|
|
||||||
def get_eventactions(db: Session,eventid: str):
|
def get_eventactions(db: Session,eventid: str):
|
||||||
#eveactions = db.query(models.Action).join(models.EventAction,models.EventAction.actionid == models.Action.id ).join(models.Event,models.Event.id == models.EventAction.eventid).filter(models.Event.eventid == eventid).all()
|
#eveactions = db.query(models.Action).join(models.EventAction,models.EventAction.actionid == models.Action.id ).join(models.Event,models.Event.id == models.EventAction.eventid).filter(models.Event.eventid == eventid).all()
|
||||||
eveactions = db.query(models.Action).join(models.EventAction,models.EventAction.actionid != models.Action.id and models.EventAction.eventid == eventid ).join(models.Event,models.Event.id == models.EventAction.eventid).filter(models.Event.eventid == eventid).all()
|
#category = get_category(db)
|
||||||
|
blackactions = (
|
||||||
|
db.query(models.EventAction.actionid)
|
||||||
|
.filter(models.EventAction.eventid == eventid)
|
||||||
|
.subquery()
|
||||||
|
)
|
||||||
|
eveactions = (
|
||||||
|
db.query(
|
||||||
|
models.Action.id,
|
||||||
|
models.Action.name,
|
||||||
|
models.Action.title,
|
||||||
|
models.Action.subtitle,
|
||||||
|
models.Action.outputpoints,
|
||||||
|
models.Action.property,
|
||||||
|
models.Action.categoryid,
|
||||||
|
models.Action.nosort,
|
||||||
|
models.Category.categoryname)
|
||||||
|
.join(models.Category,models.Category.id == models.Action.categoryid)
|
||||||
|
.filter(models.Action.id.notin_(blackactions))
|
||||||
|
.order_by(models.Category.nosort,models.Action.nosort)
|
||||||
|
.all()
|
||||||
|
)
|
||||||
|
|
||||||
if not eveactions:
|
if not eveactions:
|
||||||
raise HTTPException(status_code=404, detail="Data not found")
|
raise HTTPException(status_code=404, detail="Data not found")
|
||||||
return eveactions
|
return eveactions
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey
|
from sqlalchemy import Boolean, Column, Integer, String, DateTime,ForeignKey
|
||||||
from sqlalchemy.ext.declarative import as_declarative
|
from sqlalchemy.ext.declarative import as_declarative
|
||||||
|
from sqlalchemy.orm import relationship
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
from app.core.security import chacha20Decrypt
|
||||||
|
|
||||||
@as_declarative()
|
@as_declarative()
|
||||||
class Base:
|
class Base:
|
||||||
id = Column(Integer, primary_key=True, index=True)
|
id = Column(Integer, primary_key=True, index=True)
|
||||||
@@ -18,6 +21,16 @@ class User(Base):
|
|||||||
is_active = Column(Boolean, default=True)
|
is_active = Column(Boolean, default=True)
|
||||||
is_superuser = Column(Boolean, default=False)
|
is_superuser = Column(Boolean, default=False)
|
||||||
|
|
||||||
|
class App(Base):
|
||||||
|
__tablename__ = "app"
|
||||||
|
|
||||||
|
domainurl = Column(String(200), nullable=False)
|
||||||
|
appname = Column(String(200), nullable=False)
|
||||||
|
appid = Column(String(100), index=True, nullable=False)
|
||||||
|
version = Column(Integer)
|
||||||
|
updateuser = Column(Integer,ForeignKey("user.id"))
|
||||||
|
user = relationship('User')
|
||||||
|
|
||||||
class AppSetting(Base):
|
class AppSetting(Base):
|
||||||
__tablename__ = "appsetting"
|
__tablename__ = "appsetting"
|
||||||
|
|
||||||
@@ -40,6 +53,8 @@ class Action(Base):
|
|||||||
subtitle = Column(String(500))
|
subtitle = Column(String(500))
|
||||||
outputpoints = Column(String)
|
outputpoints = Column(String)
|
||||||
property = Column(String)
|
property = Column(String)
|
||||||
|
categoryid = Column(Integer,ForeignKey("category.id"))
|
||||||
|
nosort = Column(Integer)
|
||||||
|
|
||||||
class Flow(Base):
|
class Flow(Base):
|
||||||
__tablename__ = "flow"
|
__tablename__ = "flow"
|
||||||
@@ -47,10 +62,22 @@ class Flow(Base):
|
|||||||
flowid = Column(String(100), index=True, nullable=False)
|
flowid = Column(String(100), index=True, nullable=False)
|
||||||
appid = Column(String(100), index=True, nullable=False)
|
appid = Column(String(100), index=True, nullable=False)
|
||||||
eventid = Column(String(100), index=True, nullable=False)
|
eventid = Column(String(100), index=True, nullable=False)
|
||||||
domainid = Column(Integer,ForeignKey("domain.id"))
|
domainurl = Column(String(200))
|
||||||
name = Column(String(200))
|
name = Column(String(200))
|
||||||
content = Column(String)
|
content = Column(String)
|
||||||
|
|
||||||
|
class FlowHistory(Base):
|
||||||
|
__tablename__ = "flowhistory"
|
||||||
|
|
||||||
|
flowid = Column(String(100), index=True, nullable=False)
|
||||||
|
appid = Column(String(100), index=True, nullable=False)
|
||||||
|
eventid = Column(String(100), index=True, nullable=False)
|
||||||
|
domainurl = Column(String(200))
|
||||||
|
name = Column(String(200))
|
||||||
|
content = Column(String)
|
||||||
|
createuser = Column(Integer,ForeignKey("user.id"))
|
||||||
|
version = Column(Integer)
|
||||||
|
|
||||||
class Tenant(Base):
|
class Tenant(Base):
|
||||||
__tablename__ = "tenant"
|
__tablename__ = "tenant"
|
||||||
|
|
||||||
@@ -68,6 +95,9 @@ class Domain(Base):
|
|||||||
url = Column(String(200), nullable=False)
|
url = Column(String(200), nullable=False)
|
||||||
kintoneuser = Column(String(100), nullable=False)
|
kintoneuser = Column(String(100), nullable=False)
|
||||||
kintonepwd = Column(String(100), nullable=False)
|
kintonepwd = Column(String(100), nullable=False)
|
||||||
|
def decrypt_kintonepwd(self):
|
||||||
|
decrypted_pwd = chacha20Decrypt(self.kintonepwd)
|
||||||
|
return decrypted_pwd
|
||||||
|
|
||||||
|
|
||||||
class UserDomain(Base):
|
class UserDomain(Base):
|
||||||
@@ -90,7 +120,7 @@ class Event(Base):
|
|||||||
class EventAction(Base):
|
class EventAction(Base):
|
||||||
__tablename__ = "eventaction"
|
__tablename__ = "eventaction"
|
||||||
|
|
||||||
eventid = Column(Integer,ForeignKey("event.id"))
|
eventid = Column(String(100),ForeignKey("event.eventid"))
|
||||||
actionid = Column(Integer,ForeignKey("action.id"))
|
actionid = Column(Integer,ForeignKey("action.id"))
|
||||||
|
|
||||||
|
|
||||||
@@ -101,6 +131,17 @@ class ErrorLog(Base):
|
|||||||
location = Column(String(500))
|
location = Column(String(500))
|
||||||
content = Column(String(5000))
|
content = Column(String(5000))
|
||||||
|
|
||||||
|
class OperationLog(Base):
|
||||||
|
__tablename__ = "operationlog"
|
||||||
|
|
||||||
|
tenantid = Column(String(100))
|
||||||
|
domainurl = Column(String(200))
|
||||||
|
userid = Column(Integer,ForeignKey("user.id"))
|
||||||
|
operation = Column(String(200))
|
||||||
|
function = Column(String(200))
|
||||||
|
detail = Column(String(200))
|
||||||
|
user = relationship('User')
|
||||||
|
|
||||||
class KintoneFormat(Base):
|
class KintoneFormat(Base):
|
||||||
__tablename__ = "kintoneformat"
|
__tablename__ = "kintoneformat"
|
||||||
|
|
||||||
@@ -110,4 +151,10 @@ class KintoneFormat(Base):
|
|||||||
typecolumn =Column(Integer)
|
typecolumn =Column(Integer)
|
||||||
codecolumn =Column(Integer)
|
codecolumn =Column(Integer)
|
||||||
field = Column(String(5000))
|
field = Column(String(5000))
|
||||||
trueformat = Column(String(10))
|
trueformat = Column(String(10))
|
||||||
|
|
||||||
|
class Category(Base):
|
||||||
|
__tablename__ = "category"
|
||||||
|
|
||||||
|
categoryname = Column(String(20))
|
||||||
|
nosort = Column(Integer)
|
||||||
@@ -2,6 +2,7 @@ from pydantic import BaseModel
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
import typing as t
|
import typing as t
|
||||||
|
|
||||||
|
from app.core.security import chacha20Decrypt, chacha20Encrypt
|
||||||
|
|
||||||
class Base(BaseModel):
|
class Base(BaseModel):
|
||||||
create_time: datetime
|
create_time: datetime
|
||||||
@@ -27,21 +28,21 @@ class UserCreate(UserBase):
|
|||||||
is_active:bool
|
is_active:bool
|
||||||
is_superuser:bool
|
is_superuser:bool
|
||||||
|
|
||||||
class Config:
|
class ConfigDict:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
class UserEdit(UserBase):
|
class UserEdit(UserBase):
|
||||||
password: t.Optional[str] = None
|
password: t.Optional[str] = None
|
||||||
|
|
||||||
class Config:
|
class ConfigDict:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
class User(UserBase):
|
class User(UserBase):
|
||||||
id: int
|
id: int
|
||||||
|
|
||||||
class Config:
|
class ConfigDict:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
@@ -49,6 +50,17 @@ class Token(BaseModel):
|
|||||||
access_token: str
|
access_token: str
|
||||||
token_type: str
|
token_type: str
|
||||||
|
|
||||||
|
class AppList(Base):
|
||||||
|
domainurl: str
|
||||||
|
appname: str
|
||||||
|
appid:str
|
||||||
|
version:int
|
||||||
|
user:UserOut
|
||||||
|
|
||||||
|
class AppVersion(BaseModel):
|
||||||
|
domainurl: str
|
||||||
|
appname: str
|
||||||
|
appid:str
|
||||||
|
|
||||||
class TokenData(BaseModel):
|
class TokenData(BaseModel):
|
||||||
id:int = 0
|
id:int = 0
|
||||||
@@ -67,7 +79,7 @@ class AppBase(BaseModel):
|
|||||||
class App(AppBase):
|
class App(AppBase):
|
||||||
id: int
|
id: int
|
||||||
|
|
||||||
class Config:
|
class ConfigDict:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
|
|
||||||
@@ -78,7 +90,7 @@ class Kintone(BaseModel):
|
|||||||
desc: str = None
|
desc: str = None
|
||||||
content: str = None
|
content: str = None
|
||||||
|
|
||||||
class Config:
|
class ConfigDict:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
class Action(BaseModel):
|
class Action(BaseModel):
|
||||||
@@ -88,8 +100,10 @@ class Action(BaseModel):
|
|||||||
subtitle: str = None
|
subtitle: str = None
|
||||||
outputpoints: str = None
|
outputpoints: str = None
|
||||||
property: str = None
|
property: str = None
|
||||||
|
categoryid: int = None
|
||||||
class Config:
|
nosort: int
|
||||||
|
categoryname : str =None
|
||||||
|
class ConfigDict:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
class FlowBase(BaseModel):
|
class FlowBase(BaseModel):
|
||||||
@@ -104,11 +118,11 @@ class Flow(Base):
|
|||||||
flowid: str
|
flowid: str
|
||||||
appid: str
|
appid: str
|
||||||
eventid: str
|
eventid: str
|
||||||
domainid: int
|
domainurl: str
|
||||||
name: str = None
|
name: str = None
|
||||||
content: str = None
|
content: str = None
|
||||||
|
|
||||||
class Config:
|
class ConfigDict:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
class DomainBase(BaseModel):
|
class DomainBase(BaseModel):
|
||||||
@@ -119,6 +133,10 @@ class DomainBase(BaseModel):
|
|||||||
kintoneuser: str
|
kintoneuser: str
|
||||||
kintonepwd: str
|
kintonepwd: str
|
||||||
|
|
||||||
|
def encrypt_kintonepwd(self):
|
||||||
|
encrypted_pwd = chacha20Encrypt(self.kintonepwd)
|
||||||
|
self.kintonepwd = encrypted_pwd
|
||||||
|
|
||||||
class Domain(Base):
|
class Domain(Base):
|
||||||
id: int
|
id: int
|
||||||
tenantid: str
|
tenantid: str
|
||||||
@@ -126,8 +144,7 @@ class Domain(Base):
|
|||||||
url: str
|
url: str
|
||||||
kintoneuser: str
|
kintoneuser: str
|
||||||
kintonepwd: str
|
kintonepwd: str
|
||||||
|
class ConfigDict:
|
||||||
class Config:
|
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
class Event(Base):
|
class Event(Base):
|
||||||
@@ -139,7 +156,7 @@ class Event(Base):
|
|||||||
mobile: bool
|
mobile: bool
|
||||||
eventgroup: bool
|
eventgroup: bool
|
||||||
|
|
||||||
class Config:
|
class ConfigDict:
|
||||||
orm_mode = True
|
orm_mode = True
|
||||||
|
|
||||||
class ErrorCreate(BaseModel):
|
class ErrorCreate(BaseModel):
|
||||||
|
|||||||
@@ -24,4 +24,8 @@ python -m venv env
|
|||||||
```bash
|
```bash
|
||||||
pip install -r requirements.txt
|
pip install -r requirements.txt
|
||||||
|
|
||||||
```
|
```
|
||||||
|
4. backend 起動
|
||||||
|
```bash
|
||||||
|
uvicorn app.main:app --reload
|
||||||
|
```
|
||||||
|
|||||||
Binary file not shown.
File diff suppressed because one or more lines are too long
BIN
document/kintoneジェネレータ ログ仕様.xlsx
Normal file
BIN
document/kintoneジェネレータ ログ仕様.xlsx
Normal file
Binary file not shown.
BIN
document/kintone自動化ツールの設計書取込の仕様説明.xlsx
Normal file
BIN
document/kintone自動化ツールの設計書取込の仕様説明.xlsx
Normal file
Binary file not shown.
BIN
document/kintone項目種別一覧.xlsx
Normal file
BIN
document/kintone項目種別一覧.xlsx
Normal file
Binary file not shown.
194
document/ルックアップ同期仕様.drawio
Normal file
194
document/ルックアップ同期仕様.drawio
Normal file
File diff suppressed because one or more lines are too long
@@ -1,2 +1,6 @@
|
|||||||
# KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
|
#開発環境
|
||||||
KAB_BACKEND_URL="http://127.0.0.1:8000/"
|
#KAB_BACKEND_URL="https://kab-backend.azurewebsites.net/"
|
||||||
|
#単体テスト環境
|
||||||
|
#KAB_BACKEND_URL="https://kab-backend-unittest.azurewebsites.net/"
|
||||||
|
#ローカル開発環境
|
||||||
|
KAB_BACKEND_URL="http://127.0.0.1:8000/"
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
<html lang="ja-jp">
|
<html lang="ja-jp">
|
||||||
<head>
|
<head>
|
||||||
<title><%= productName %></title>
|
<title><%= productName %></title>
|
||||||
|
|
||||||
<meta charset="utf-8">
|
<meta charset="utf-8">
|
||||||
<meta name="description" content="<%= productDescription %>">
|
<meta name="description" content="<%= productDescription %>">
|
||||||
<meta name="format-detection" content="telephone=no">
|
<meta name="format-detection" content="telephone=no">
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "kintone-automate",
|
"name": "k-tune",
|
||||||
"version": "0.2.0",
|
"version": "0.2.0",
|
||||||
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
|
"description": "Kintoneアプリの自動生成とデプロイを支援ツールです",
|
||||||
"productName": "kintone Automate",
|
"productName": "k-tune | kintoneジェネレーター",
|
||||||
"author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>",
|
"author": "maxiaozhe@alicorns.co.jp <maxiaozhe@alicorns.co.jp>",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
@@ -12,13 +12,15 @@
|
|||||||
"dev": "quasar dev",
|
"dev": "quasar dev",
|
||||||
"dev:local": "set \"LOCAL=true\" && quasar dev",
|
"dev:local": "set \"LOCAL=true\" && quasar dev",
|
||||||
"build": "set \"SOURCE_MAP=false\" && quasar build",
|
"build": "set \"SOURCE_MAP=false\" && quasar build",
|
||||||
"build:dev":"set \"SOURCE_MAP=true\" && quasar build"
|
"build:dev": "set \"SOURCE_MAP=true\" && quasar build"
|
||||||
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@quasar/extras": "^1.16.4",
|
"@quasar/extras": "^1.16.4",
|
||||||
|
"@vueuse/core": "^10.9.0",
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"pinia": "^2.1.6",
|
"jwt-decode": "^4.0.0",
|
||||||
|
"pinia": "^2.1.7",
|
||||||
|
"pinia-plugin-persistedstate": "^3.2.1",
|
||||||
"quasar": "^2.6.0",
|
"quasar": "^2.6.0",
|
||||||
"uuid": "^9.0.0",
|
"uuid": "^9.0.0",
|
||||||
"vue": "^3.0.0",
|
"vue": "^3.0.0",
|
||||||
|
|||||||
@@ -94,6 +94,7 @@ module.exports = configure(function (/* ctx */) {
|
|||||||
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
// Full list of options: https://v2.quasar.dev/quasar-cli-vite/quasar-config-js#devServer
|
||||||
devServer: {
|
devServer: {
|
||||||
// https: true
|
// https: true
|
||||||
|
port:9001,
|
||||||
open: true, // opens browser window automatically
|
open: true, // opens browser window automatically
|
||||||
env: { ...dotenv },
|
env: { ...dotenv },
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { boot } from 'quasar/wrappers';
|
|||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
import {router} from 'src/router';
|
import {router} from 'src/router';
|
||||||
|
|
||||||
|
|
||||||
declare module '@vue/runtime-core' {
|
declare module '@vue/runtime-core' {
|
||||||
interface ComponentCustomProperties {
|
interface ComponentCustomProperties {
|
||||||
$axios: AxiosInstance;
|
$axios: AxiosInstance;
|
||||||
@@ -15,30 +16,10 @@ declare module '@vue/runtime-core' {
|
|||||||
// good idea to move this instance creation inside of the
|
// good idea to move this instance creation inside of the
|
||||||
// "export default () => {}" function below (which runs individually
|
// "export default () => {}" function below (which runs individually
|
||||||
// for each client)
|
// for each client)
|
||||||
|
|
||||||
const api:AxiosInstance = axios.create({ baseURL: process.env.KAB_BACKEND_URL });
|
const api:AxiosInstance = axios.create({ baseURL: process.env.KAB_BACKEND_URL });
|
||||||
const token=localStorage.getItem('token')||'';
|
|
||||||
if(token!==''){
|
|
||||||
api.defaults.headers["Authorization"]='Bearer ' + token;
|
|
||||||
}
|
|
||||||
//axios例外キャプチャー
|
|
||||||
api.interceptors.response.use(
|
|
||||||
(response)=>response,
|
|
||||||
(error)=>{
|
|
||||||
if (error.response && error.response.status === 401) {
|
|
||||||
// 認証エラーの場合再ログインする
|
|
||||||
console.error('(; ゚Д゚)/認証エラー(401):', error);
|
|
||||||
localStorage.removeItem('token');
|
|
||||||
router.replace({
|
|
||||||
path:"/login",
|
|
||||||
query:{redirect:router.currentRoute.value.fullPath}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
)
|
|
||||||
export default boot(({ app }) => {
|
export default boot(({ app }) => {
|
||||||
// for use inside Vue files (Options API) through this.$axios and this.$api
|
// for use inside Vue files (Options API) through this.$axios and this.$api
|
||||||
|
|
||||||
app.config.globalProperties.$axios = axios;
|
app.config.globalProperties.$axios = axios;
|
||||||
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
|
// ^ ^ ^ this will allow you to use this.$axios (for Vue Options API form)
|
||||||
// so you won't necessarily have to import axios in each vue file
|
// so you won't necessarily have to import axios in each vue file
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import { Router } from 'vue-router';
|
|||||||
import { App } from 'vue';
|
import { App } from 'vue';
|
||||||
|
|
||||||
export default boot(({ app, router }: { app: App<Element>; router: Router }) => {
|
export default boot(({ app, router }: { app: App<Element>; router: Router }) => {
|
||||||
|
document.documentElement.lang="ja-JP";
|
||||||
app.config.errorHandler = (err: any, instance: any, info: string) => {
|
app.config.errorHandler = (err: any, instance: any, info: string) => {
|
||||||
if (err.response && err.response.status === 401) {
|
if (err.response && err.response.status === 401) {
|
||||||
// 認証エラーの場合再ログインする
|
// 認証エラーの場合再ログインする
|
||||||
|
|||||||
@@ -3,20 +3,46 @@
|
|||||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||||
<q-spinner color="primary" size="3em" />
|
<q-spinner color="primary" size="3em" />
|
||||||
</div>
|
</div>
|
||||||
<q-table v-else row-key="index" :selection="type" v-model:selected="selected" :columns="columns" :rows="rows"
|
<q-splitter
|
||||||
class="action-table"
|
v-model="splitterModel"
|
||||||
flat bordered
|
style="height: 100%"
|
||||||
virtual-scroll
|
before-class="tab"
|
||||||
:pagination="pagination"
|
unit="px"
|
||||||
:rows-per-page-options="[0]"
|
v-else
|
||||||
:filter="filter"
|
|
||||||
>
|
>
|
||||||
</q-table>
|
<template v-slot:before>
|
||||||
|
<q-tabs
|
||||||
|
v-model="tab"
|
||||||
|
vertical
|
||||||
|
active-color="white"
|
||||||
|
indicator-color="primary"
|
||||||
|
active-bg-color="primary"
|
||||||
|
class="bg-grey-2 text-grey-8"
|
||||||
|
dense
|
||||||
|
>
|
||||||
|
<q-tab :name="cate"
|
||||||
|
:label="cate"
|
||||||
|
v-for="(cate,) in categorys"
|
||||||
|
:key="cate"
|
||||||
|
></q-tab>
|
||||||
|
</q-tabs>
|
||||||
|
</template>
|
||||||
|
<template v-slot:after>
|
||||||
|
<q-table row-key="index" :selection="type" v-model:selected="selected" :columns="columns" :rows="actionForTab"
|
||||||
|
class="action-table"
|
||||||
|
flat bordered
|
||||||
|
virtual-scroll
|
||||||
|
:pagination="pagination"
|
||||||
|
:rows-per-page-options="[0]"
|
||||||
|
:filter="filter"></q-table>
|
||||||
|
</template>
|
||||||
|
</q-splitter>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script>
|
||||||
import { ref,onMounted,reactive } from 'vue'
|
import { ref,onMounted,reactive,watchEffect,computed,watch } from 'vue'
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'actionSelect',
|
name: 'actionSelect',
|
||||||
@@ -25,30 +51,74 @@ export default {
|
|||||||
type: String,
|
type: String,
|
||||||
filter:String
|
filter:String
|
||||||
},
|
},
|
||||||
setup(props) {
|
emits:[
|
||||||
|
"clearFilter"
|
||||||
|
],
|
||||||
|
setup(props,{emit}) {
|
||||||
const isLoaded=ref(false);
|
const isLoaded=ref(false);
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
|
{ name: 'name', required: true,label: 'アクション名',align: 'left',field: 'name',sortable: true},
|
||||||
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
|
{ name: 'desc', align: 'left', label: '説明', field: 'desc', sortable: true },
|
||||||
// { name: 'content', label: '内容', field: 'content', sortable: true }
|
// { name: 'content', label: '内容', field: 'content', sortable: true }
|
||||||
];
|
];
|
||||||
const rows = reactive([])
|
const store = useFlowEditorStore();
|
||||||
|
let actionData =reactive([]);
|
||||||
|
const categorys = ref('');
|
||||||
|
const tab=ref('');
|
||||||
|
const actionForTab=computed(()=>{
|
||||||
|
const rows=[];
|
||||||
|
const actions= props.filter? actionData:actionData.filter(x=>x.categoryname===tab.value);
|
||||||
|
actions.forEach((item,index) =>{
|
||||||
|
rows.push({index,
|
||||||
|
name:item.name,
|
||||||
|
desc:item.title,
|
||||||
|
outputPoints:item.outputpoints,
|
||||||
|
property:item.property});
|
||||||
|
});
|
||||||
|
return rows;
|
||||||
|
});
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const res =await api.get('api/actions');
|
let eventId='';
|
||||||
res.data.forEach((item,index) =>
|
if(store.selectedEvent ){
|
||||||
{
|
eventId=store.selectedEvent.header!=='DELETABLE'? store.selectedEvent.eventId : store.selectedEvent.parentId;
|
||||||
rows.push({index,name:item.name,desc:item.title,outputPoints:item.outputpoints,property:item.property});
|
}
|
||||||
});
|
const res =await api.get(`api/eventactions/${eventId}`);
|
||||||
|
actionData= res.data;
|
||||||
|
const categoryNames = Array.from(new Set(actionData.map(x=>x.categoryname)));
|
||||||
|
categorys.value=categoryNames;
|
||||||
|
tab.value = categoryNames.length>0? categoryNames[0]:'';
|
||||||
isLoaded.value=true;
|
isLoaded.value=true;
|
||||||
});
|
});
|
||||||
|
// watch(props.filter,()=>{
|
||||||
|
// if(props.filter && props.filter!==''){
|
||||||
|
// tab.value='';
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
watch(tab,()=>{
|
||||||
|
if(tab.value!==''){
|
||||||
|
emit('clearFilter','');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
// watchEffect(()=>{
|
||||||
|
// if(props.filter && props.filter!==''){
|
||||||
|
// tab.value='';
|
||||||
|
// }
|
||||||
|
// if(tab.value!==''){
|
||||||
|
// emit('update:filter','');
|
||||||
|
// }
|
||||||
|
// });
|
||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
rows,
|
|
||||||
selected: ref([]),
|
selected: ref([]),
|
||||||
pagination:ref({
|
pagination:ref({
|
||||||
rowsPerPage:0
|
rowsPerPage:0
|
||||||
}),
|
}),
|
||||||
isLoaded,
|
isLoaded,
|
||||||
|
tab,
|
||||||
|
actionData,
|
||||||
|
categorys,
|
||||||
|
splitterModel: ref(150),
|
||||||
|
actionForTab
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -58,5 +128,6 @@ export default {
|
|||||||
.action-table{
|
.action-table{
|
||||||
min-height: 10vh;
|
min-height: 10vh;
|
||||||
max-height: 68vh;
|
max-height: 68vh;
|
||||||
|
min-width: 550px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
131
frontend/src/components/AppFieldSelectBox.vue
Normal file
131
frontend/src/components/AppFieldSelectBox.vue
Normal file
@@ -0,0 +1,131 @@
|
|||||||
|
<template>
|
||||||
|
|
||||||
|
<div class="q-mx-md q-mb-lg">
|
||||||
|
<div class="q-mb-xs q-ml-md text-primary">アプリ選択</div>
|
||||||
|
|
||||||
|
<div class="q-pa-md row" style="border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 4px;">
|
||||||
|
<div v-if="selField?.app && !showSelectApp">{{ selField?.app?.name }}</div>
|
||||||
|
<q-space />
|
||||||
|
<div>
|
||||||
|
<q-btn outline dense label="選 択" padding="none sm" color="primary" @click="() => {
|
||||||
|
showSelectApp = true;
|
||||||
|
}"></q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div v-if="!showSelectApp && selField?.app?.name">
|
||||||
|
<div>
|
||||||
|
<div class="row q-mb-md">
|
||||||
|
<!-- <div class="col"> -->
|
||||||
|
<div class="q-mb-xs q-ml-md text-primary">フィールド選択</div>
|
||||||
|
<!-- </div> -->
|
||||||
|
<q-space />
|
||||||
|
<!-- <div class="col"> -->
|
||||||
|
<div class="q-mr-md">
|
||||||
|
<q-input dense debounce="300" v-model="fieldFilter" placeholder="フィールド検索" clearable>
|
||||||
|
<template v-slot:before>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelectFields="updateSelectFields"
|
||||||
|
:appId="selField?.app?.id" not_page :filter="fieldFilter"
|
||||||
|
:selectedFields="selField.fields" :fieldTypes="fieldTypes"></field-select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="min-width: 45vw;" v-else>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
|
<show-dialog v-model:visible="showSelectApp" name="アプリ選択">
|
||||||
|
<template v-slot:toolbar>
|
||||||
|
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
||||||
|
<template v-slot:before>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<AppSelectBox ref="appDlg" name="アプリ" type="single" :filter="filter"
|
||||||
|
:updateSelectApp="updateSelectApp"></AppSelectBox>
|
||||||
|
</show-dialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref, watchEffect, computed, reactive } from 'vue';
|
||||||
|
import ShowDialog from './ShowDialog.vue';
|
||||||
|
import FieldSelect from './FieldSelect.vue';
|
||||||
|
import AppSelectBox from './AppSelectBox.vue';
|
||||||
|
interface IApp {
|
||||||
|
id: string,
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
interface IField {
|
||||||
|
name: string,
|
||||||
|
code: string,
|
||||||
|
type: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IAppFields {
|
||||||
|
app?: IApp,
|
||||||
|
fields: IField[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
inheritAttrs: false,
|
||||||
|
name: 'AppFieldSelectBox',
|
||||||
|
components: {
|
||||||
|
ShowDialog,
|
||||||
|
FieldSelect,
|
||||||
|
AppSelectBox,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
selectedField: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
selectType: {
|
||||||
|
type: String,
|
||||||
|
default: 'single'
|
||||||
|
},
|
||||||
|
fieldTypes:{
|
||||||
|
type:Array,
|
||||||
|
default:()=>[]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const showSelectApp = ref(false);
|
||||||
|
const selField = reactive(props.selectedField);
|
||||||
|
|
||||||
|
const isSelected = computed(() => {
|
||||||
|
return selField !== null && typeof selField === 'object' && ('app' in selField)
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateSelectApp = (newAppinfo: IApp) => {
|
||||||
|
selField.app = newAppinfo
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateSelectFields = (newFields: IField[]) => {
|
||||||
|
selField.fields = newFields
|
||||||
|
}
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
emit('update:modelValue', selField);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
showSelectApp,
|
||||||
|
isSelected,
|
||||||
|
updateSelectApp,
|
||||||
|
filter: ref(),
|
||||||
|
updateSelectFields,
|
||||||
|
fieldFilter: ref(),
|
||||||
|
selField
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</q-field>
|
</q-field>
|
||||||
<q-field stack-label full-width label="アプリ説明">
|
<q-field stack-label full-width label="アプリの説明">
|
||||||
<template v-slot:control>
|
<template v-slot:control>
|
||||||
<div class="self-center full-width no-outline" tabindex="0">
|
<div class="self-center full-width no-outline" tabindex="0">
|
||||||
{{ appinfo?.description }}
|
{{ appinfo?.description }}
|
||||||
|
|||||||
@@ -21,12 +21,12 @@ import { ref, onMounted, reactive, watchEffect } from 'vue'
|
|||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'AppSelect',
|
name: 'AppSelectBox',
|
||||||
props: {
|
props: {
|
||||||
name: String,
|
name: String,
|
||||||
type: String,
|
type: String,
|
||||||
filter: String,
|
filter: String,
|
||||||
updateExternalSelectAppInfo: {
|
updateSelectApp: {
|
||||||
type: Function
|
type: Function
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -42,8 +42,8 @@ export default {
|
|||||||
const selected = ref([])
|
const selected = ref([])
|
||||||
|
|
||||||
watchEffect(()=>{
|
watchEffect(()=>{
|
||||||
if (selected.value && selected.value[0] && props.updateExternalSelectAppInfo) {
|
if (selected.value && selected.value[0] && props.updateSelectApp) {
|
||||||
props.updateExternalSelectAppInfo(selected.value[0])
|
props.updateSelectApp(selected.value[0])
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
271
frontend/src/components/CascadingDropDownBox.vue
Normal file
271
frontend/src/components/CascadingDropDownBox.vue
Normal file
@@ -0,0 +1,271 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<q-stepper v-model="step" ref="stepper" color="primary" animated flat>
|
||||||
|
<q-step :name="1" title="データソースの設定" icon="app_registration" :done="step > 1">
|
||||||
|
<div class="row justify-between items-center">
|
||||||
|
<div>アプリの選択 :</div>
|
||||||
|
<div>
|
||||||
|
<a v-if="data.sourceApp?.name" class="q-mr-xs"
|
||||||
|
:href="data.sourceApp ? `${authStore.currentDomain.kintoneUrl}/k/${data.sourceApp.id}` : ''"
|
||||||
|
target="_blank" title="Kiontoneへ">
|
||||||
|
{{ data.sourceApp?.name }}
|
||||||
|
</a>
|
||||||
|
<div v-else class="text-red">APPを選択してください</div>
|
||||||
|
<q-btn v-if="data.sourceApp?.name" flat color="grey" icon="clear" size="sm" padding="none"
|
||||||
|
@click="clearSelectedApp" />
|
||||||
|
</div>
|
||||||
|
<q-btn outline dense label="変更" padding="xs sm" color="primary" @click="showAppDialog" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- フィールド設定部分 -->
|
||||||
|
<template v-if="data.sourceApp?.name">
|
||||||
|
<q-separator class="q-mt-md" />
|
||||||
|
<div class="q-my-md row justify-between items-center">
|
||||||
|
データ階層を設定する :
|
||||||
|
<q-btn icon="add" size="sm" padding="xs" outline color="primary" @click="addRow" />
|
||||||
|
</div>
|
||||||
|
<q-virtual-scroll style="max-height: 13.5rem;" :items="data.fieldList" separator v-slot="{ item, index }">
|
||||||
|
<div class="row justify-between items-center q-my-md">
|
||||||
|
<div>{{ index + 1 }}階層 :</div>
|
||||||
|
<div>{{ item.source?.name }}</div>
|
||||||
|
<q-btn-group outline>
|
||||||
|
<q-btn outline dense label="変更" padding="xs sm" color="primary"
|
||||||
|
@click="() => showFieldDialog(item, 'source')" />
|
||||||
|
<q-btn outline dense label="削除" padding="xs sm" color="primary" @click="() => delRow(index)" />
|
||||||
|
</q-btn-group>
|
||||||
|
</div>
|
||||||
|
</q-virtual-scroll>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<!-- アプリ選択ダイアログ -->
|
||||||
|
<ShowDialog v-model:visible="data.sourceApp.showSelectApp" name="アプリ選択" @close="closeAppDialog" min-width="50vw"
|
||||||
|
min-height="50vh">
|
||||||
|
<template v-slot:toolbar>
|
||||||
|
<q-input dense debounce="300" v-model="data.sourceApp.appFilter" placeholder="検索" clearable>
|
||||||
|
<template v-slot:before>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="data.sourceApp.appFilter" />
|
||||||
|
</ShowDialog>
|
||||||
|
</q-step>
|
||||||
|
|
||||||
|
<q-step :name="2" title="ドロップダウンフィールドの設定" icon="multiple_stop" :done="step > 2">
|
||||||
|
<div class="row q-pa-sm q-col-gutter-x-sm flex-center">
|
||||||
|
<div class="col-grow row q-col-gutter-x-sm">
|
||||||
|
<div class="col-6">データソース</div>
|
||||||
|
<div class="col-6">ドロップダウンフィールド</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<div style="width: 88px; height: 1px;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div v-for="(item) in data.fieldList" :key="item.id" class="row q-pa-sm q-col-gutter-x-sm flex-center">
|
||||||
|
<div class="col-grow row q-col-gutter-x-sm">
|
||||||
|
|
||||||
|
<div class="col-6">{{ item.source.name }}</div>
|
||||||
|
<div class="col-6">{{ item.dropDown?.name }}</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<div class="row justify-end">
|
||||||
|
<q-btn-group outline>
|
||||||
|
<q-btn outline dense label="設定" padding="xs sm" color="primary"
|
||||||
|
@click="() => showFieldDialog(item, 'dropDown')" />
|
||||||
|
<q-btn outline dense label="クリア" padding="xs sm" color="primary"
|
||||||
|
@click="() => item.dropDown = undefined" />
|
||||||
|
</q-btn-group>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</q-step>
|
||||||
|
|
||||||
|
<!-- ステップナビゲーション -->
|
||||||
|
<template v-slot:navigation>
|
||||||
|
<q-stepper-navigation>
|
||||||
|
<div class="row justify-end q-mt-md">
|
||||||
|
<q-btn v-if="step > 1" flat color="primary" @click="$refs.stepper.previous()" label="戻る" class="q-ml-sm" />
|
||||||
|
<q-btn @click="stepperNext" color="primary" :label="step === 2 ? '確定' : '次へ'"
|
||||||
|
:disable="nextBtnCheck()" />
|
||||||
|
</div>
|
||||||
|
</q-stepper-navigation>
|
||||||
|
</template>
|
||||||
|
</q-stepper>
|
||||||
|
|
||||||
|
<!-- フィールド選択ダイアログ -->
|
||||||
|
<template v-for="(item, index) in data.fieldList" :key="`dg${item.id}`">
|
||||||
|
<show-dialog v-model:visible="item.sourceDg.show" name="フィールド一覧" min-width="400px">
|
||||||
|
<template v-slot:toolbar>
|
||||||
|
<q-input dense debounce="300" v-model="item.sourceDg.filter" placeholder="検索" clearable>
|
||||||
|
<template v-slot:before>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
<FieldSelect name="フィールド" :appId="data.sourceApp.id" :selectedFields="item.source"
|
||||||
|
:filter="item.sourceDg.filter" :updateSelectFields="(f) => updateSelectField(f, item, index, 'source')"
|
||||||
|
:blackListLabel="blackListLabel" />
|
||||||
|
</show-dialog>
|
||||||
|
|
||||||
|
<show-dialog v-model:visible="item.dropDownDg.show" name="フィールド一覧" min-width="400px">
|
||||||
|
<template v-slot:toolbar>
|
||||||
|
<q-input dense debounce="300" v-model="item.dropDownDg.filter" placeholder="検索" clearable>
|
||||||
|
<template v-slot:before>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
<FieldSelect name="フィールド" :appId="data.dropDownApp.id" :selectedFields="item.source"
|
||||||
|
:filter="item.dropDownDg.filter" :updateSelectFields="(f) => updateSelectField(f, item, index, 'dropDown')"
|
||||||
|
:blackListLabel="blackListLabel" />
|
||||||
|
</show-dialog>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, reactive, ref, watchEffect,watch } from 'vue';
|
||||||
|
import ShowDialog from './ShowDialog.vue';
|
||||||
|
import AppSelectBox from './AppSelectBox.vue';
|
||||||
|
import FieldSelect from './FieldSelect.vue';
|
||||||
|
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||||
|
import { useFlowEditorStore } from 'src/stores/flowEditor';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { useQuasar } from 'quasar';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'CascadingDropDownBox',
|
||||||
|
inheritAttrs: false,
|
||||||
|
components: { ShowDialog, AppSelectBox, FieldSelect },
|
||||||
|
props: {
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({})
|
||||||
|
},
|
||||||
|
finishDialogHandler: Function,
|
||||||
|
},
|
||||||
|
emits: ['update:modelValue'],
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const flowStore = useFlowEditorStore();
|
||||||
|
const $q = useQuasar();
|
||||||
|
const appDg = ref();
|
||||||
|
const stepper = ref();
|
||||||
|
const step = ref(1);
|
||||||
|
|
||||||
|
const data =ref(props.modelValue);
|
||||||
|
// const data = ref({
|
||||||
|
// sourceApp: props.modelValue.sourceApp ?? { appFilter: '', showSelectApp: false },
|
||||||
|
// dropDownApp: props.modelValue.dropDownApp,
|
||||||
|
// fieldList: props.modelValue.fieldList ?? [],
|
||||||
|
// });
|
||||||
|
|
||||||
|
// アプリ関連の関数
|
||||||
|
const showAppDialog = () => data.value.sourceApp.showSelectApp = true;
|
||||||
|
|
||||||
|
const clearSelectedApp = () => {
|
||||||
|
data.value.sourceApp = { appFilter: '', showSelectApp: false };
|
||||||
|
data.value.fieldList = [];
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeAppDialog = (val: 'OK' | 'Cancel') => {
|
||||||
|
data.value.sourceApp.showSelectApp = false;
|
||||||
|
const selected = appDg.value?.selected[0];
|
||||||
|
if (val === 'OK' && selected) {
|
||||||
|
if (flowStore.appInfo?.appId === selected.id) {
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
caption: "エラー",
|
||||||
|
message: 'データソースを現在のアプリにすることはできません。'
|
||||||
|
});
|
||||||
|
} else if (selected.id !== data.value.sourceApp.id) {
|
||||||
|
clearSelectedApp();
|
||||||
|
Object.assign(data.value.sourceApp, { id: selected.id, name: selected.name });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// フィールド関連の関数
|
||||||
|
const defaultRow = () => ({
|
||||||
|
id: uuidv4(),
|
||||||
|
source: undefined,
|
||||||
|
dropDown: undefined,
|
||||||
|
sourceDg: { show: false, filter: '' },
|
||||||
|
dropDownDg: { show: false, filter: '' },
|
||||||
|
});
|
||||||
|
|
||||||
|
const addRow = () => data.value.fieldList.push(defaultRow());
|
||||||
|
const delRow = (index: number) => data.value.fieldList.splice(index, 1);
|
||||||
|
|
||||||
|
const showFieldDialog = (item: any, keyName: string) => item[`${keyName}Dg`].show = true;
|
||||||
|
|
||||||
|
const updateSelectField = (f: any, item: any, index: number, keyName: 'source' | 'dropDown') => {
|
||||||
|
const [selected] = f.value;
|
||||||
|
const isDuplicate = data.value.fieldList.some((field, idx) =>
|
||||||
|
idx !== index && (field[keyName]?.code === selected.code || field[keyName]?.label === selected.label)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (isDuplicate) {
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
caption: "エラー",
|
||||||
|
message: '重複したフィールドは選択できません'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
item[keyName] = selected;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// ステッパー関連の関数
|
||||||
|
const nextBtnCheck = () => {
|
||||||
|
const stepNo = step.value
|
||||||
|
if (stepNo === 1) {
|
||||||
|
return !(data.value.sourceApp?.id && data.value.fieldList?.length > 0 && data.value.fieldList?.every(f => f.source?.name));
|
||||||
|
} else if (stepNo === 2) {
|
||||||
|
return !data.value.fieldList?.every(f => f.dropDown?.name);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const stepperNext = () => {
|
||||||
|
if (step.value === 2) {
|
||||||
|
props.finishDialogHandler?.(data.value);
|
||||||
|
} else {
|
||||||
|
data.value.dropDownApp = { name: flowStore.appInfo?.name, id: flowStore.appInfo?.appId };
|
||||||
|
stepper.value?.next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// // データ変更の監視
|
||||||
|
// watchEffect(() =>{
|
||||||
|
// emit('update:modelValue', data.value);
|
||||||
|
// });
|
||||||
|
|
||||||
|
return {
|
||||||
|
// 状態と参照
|
||||||
|
authStore,
|
||||||
|
step,
|
||||||
|
stepper,
|
||||||
|
appDg,
|
||||||
|
data,
|
||||||
|
// アプリ関連の関数
|
||||||
|
showAppDialog,
|
||||||
|
closeAppDialog,
|
||||||
|
clearSelectedApp,
|
||||||
|
|
||||||
|
// フィールド関連の関数
|
||||||
|
addRow,
|
||||||
|
delRow,
|
||||||
|
showFieldDialog,
|
||||||
|
updateSelectField,
|
||||||
|
|
||||||
|
// ステッパー関連の関数
|
||||||
|
nextBtnCheck,
|
||||||
|
stepperNext,
|
||||||
|
|
||||||
|
// 定数
|
||||||
|
blackListLabel: ['レコード番号', '作業者', '更新者', '更新日時', '作成日時', '作成者', 'カテゴリー', 'ステータス'],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
<template>
|
<template>
|
||||||
<show-dialog v-model:visible="showflg" name="条件エディタ" @close="closeDg" min-width="60vw" min-height="60vh">
|
<show-dialog v-model:visible="showflg" name="条件エディタ" @close="closeDg" min-width="50vw" min-height="60vh">
|
||||||
<template v-slot:toolbar>
|
<template v-slot:toolbar>
|
||||||
<q-btn flat round dense icon="more_vert" >
|
<q-btn flat round dense icon="more_vert" >
|
||||||
<q-menu auto-close anchor="bottom start">
|
<q-menu auto-close anchor="bottom start">
|
||||||
@@ -52,12 +52,12 @@ import { useQuasar } from 'quasar';
|
|||||||
const tree = ref(props.conditionTree);
|
const tree = ref(props.conditionTree);
|
||||||
const closeDg = (val:string) => {
|
const closeDg = (val:string) => {
|
||||||
if (val == 'OK') {
|
if (val == 'OK') {
|
||||||
if(tree.value.root.children.length===0){
|
// if(tree.value.root.children.length===0){
|
||||||
$q.notify({
|
// $q.notify({
|
||||||
type: 'negative',
|
// type: 'negative',
|
||||||
message: `条件式を設定してください。`
|
// message: `条件式を設定してください。`
|
||||||
});
|
// });
|
||||||
}
|
// }
|
||||||
context.emit("update:conditionTree",tree.value);
|
context.emit("update:conditionTree",tree.value);
|
||||||
}
|
}
|
||||||
showflg.value=false;
|
showflg.value=false;
|
||||||
|
|||||||
@@ -1,96 +1,144 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-field v-model="selectedObject" labelColor="primary" class="condition-object"
|
<q-field labelColor="primary" class="condition-object" dense outlined :label="label" :disable="disabled"
|
||||||
:clearable="isSelected" stack-label :dense="true" :outlined="true" >
|
:clearable="isSelected">
|
||||||
<template v-slot:control >
|
<template v-slot:control>
|
||||||
<q-chip color="primary" text-color="white" v-if="isSelected && selectedObject.objectType==='field'" :dense="true" class="selected-obj">
|
<q-chip color="primary" text-color="white" v-if="isSelected && selectedObject.objectType==='field'" :dense="true" class="selected-obj">
|
||||||
{{ selectedObject.name }}
|
{{ selectedObject.name }}
|
||||||
</q-chip>
|
</q-chip>
|
||||||
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
|
<q-chip color="info" text-color="white" v-if="isSelected && selectedObject.objectType==='variable'" :dense="true" class="selected-obj">
|
||||||
{{ selectedObject.name }}
|
{{ selectedObject.name.name }}
|
||||||
</q-chip>
|
</q-chip>
|
||||||
</template>
|
<div v-if="isSelected && selectedObject.objectType==='text'">{{ selectedObject?.sharedText }}</div>
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon name="search" class="cursor-pointer" @click="showDg"/>
|
|
||||||
</template>
|
|
||||||
</q-field>
|
|
||||||
<show-dialog v-model:visible="show" name="条件設定項目一覧" @close="closeDg" width="600px">
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
</template>
|
||||||
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
|
<template v-slot:append>
|
||||||
|
<q-icon name="search" class="cursor-pointer" @click="showDg" />
|
||||||
|
</template>
|
||||||
|
</q-field>
|
||||||
|
<show-dialog v-model:visible="show" name="設定項目" @close="closeDg" min-width="400px">
|
||||||
|
<!-- <template v-slot:toolbar>
|
||||||
|
<q-input dense debounce="200" v-model="filter" placeholder="検索" clearable>
|
||||||
|
<template v-slot:before>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
<condition-objects ref="appDg" name="フィールド" type="single" :filter="filter" :appId="store.appInfo?.appId" :vars="vars"></condition-objects>
|
||||||
|
-->
|
||||||
|
<DynamicItemInput v-model:selectedObject="selectedObject" :canInput="config.canInput"
|
||||||
|
:buttonsConfig="config.buttonsConfig" :appId="store.appInfo?.appId" :options="options" ref="inputRef" />
|
||||||
|
|
||||||
</show-dialog>
|
</show-dialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, reactive, ref ,watchEffect,computed} from 'vue';
|
import { defineComponent, reactive, ref, watchEffect, computed ,PropType} from 'vue';
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
import ShowDialog from '../ShowDialog.vue';
|
||||||
import ConditionObjects from '../ConditionObjects.vue';
|
// import ConditionObjects from '../ConditionObjects.vue';
|
||||||
import { useFlowEditorStore } from '../../stores/flowEditor';
|
import DynamicItemInput from '../DynamicItemInput/DynamicItemInput.vue';
|
||||||
import {IActionFlow,IActionNode,IActionVariable} from '../../types/ActionTypes';
|
import { useFlowEditorStore } from '../../stores/flowEditor';
|
||||||
export default defineComponent({
|
import { IActionFlow, IActionNode, IActionVariable } from '../../types/ActionTypes';
|
||||||
name: 'ConditionObject',
|
import { IDynamicInputConfig } from 'src/types/ComponentTypes';
|
||||||
components: {
|
|
||||||
ShowDialog,
|
|
||||||
ConditionObjects
|
export default defineComponent({
|
||||||
|
name: 'ConditionObject',
|
||||||
|
components: {
|
||||||
|
ShowDialog,
|
||||||
|
DynamicItemInput,
|
||||||
|
// ConditionObjects
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
},
|
},
|
||||||
props: {
|
label: {
|
||||||
modelValue: {
|
type: String,
|
||||||
type: Object,
|
default: undefined
|
||||||
default: null
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
config: {
|
||||||
const appDg = ref();
|
type: Object as PropType<IDynamicInputConfig>,
|
||||||
const show = ref(false);
|
default: () => {
|
||||||
const selectedObject = ref(props.modelValue);
|
return {
|
||||||
const store = useFlowEditorStore();
|
canInput: false,
|
||||||
const isSelected = computed(()=>{
|
buttonsConfig: [
|
||||||
return selectedObject.value!==null && typeof selectedObject.value === 'object' && ('name' in selectedObject.value)
|
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
||||||
});
|
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
||||||
let vars:IActionVariable[] =[];
|
]
|
||||||
if(store.currentFlow!==undefined && store.activeNode!==undefined ){
|
};
|
||||||
vars =store.currentFlow.getVarNames(store.activeNode);
|
|
||||||
}
|
}
|
||||||
const filter=ref('');
|
},
|
||||||
const showDg = () => {
|
|
||||||
show.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeDg = (val:string) => {
|
options:
|
||||||
if (val == 'OK') {
|
{
|
||||||
selectedObject.value = appDg.value.selected[0];
|
type:Array as PropType< string[]>,
|
||||||
}
|
default:()=>[]
|
||||||
};
|
},
|
||||||
|
modelValue: {
|
||||||
watchEffect(() => {
|
type: Object,
|
||||||
emit('update:modelValue', selectedObject.value);
|
default: null
|
||||||
});
|
},
|
||||||
|
},
|
||||||
return {
|
setup(props, { emit }) {
|
||||||
store,
|
// const appDg = ref();
|
||||||
appDg,
|
const inputRef=ref();
|
||||||
show,
|
const show = ref(false);
|
||||||
showDg,
|
const selectedObject = ref(props.modelValue);
|
||||||
closeDg,
|
const store = useFlowEditorStore();
|
||||||
selectedObject,
|
// const sharedText = ref(''); // 共享的文本状态
|
||||||
vars:reactive(vars),
|
const isSelected = computed(() => {
|
||||||
isSelected,
|
return selectedObject.value?.sharedText !== '';
|
||||||
filter
|
});
|
||||||
};
|
// const isSelected = computed(()=>{
|
||||||
|
// return selectedObject.value!==null && typeof selectedObject.value === 'object' && ('name' in selectedObject.value)
|
||||||
|
// });
|
||||||
|
let vars: IActionVariable[] = [];
|
||||||
|
if (store.currentFlow !== undefined && store.activeNode !== undefined) {
|
||||||
|
vars = store.currentFlow.getVarNames(store.activeNode);
|
||||||
}
|
}
|
||||||
});
|
// const filter=ref('');
|
||||||
</script>
|
const showDg = () => {
|
||||||
|
show.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDg = (val: string) => {
|
||||||
|
if (val == 'OK') {
|
||||||
|
// selectedObject.value = appDg.value.selected[0];
|
||||||
|
selectedObject.value = inputRef.value.selectedObjectRef
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
emit('update:modelValue', selectedObject.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
inputRef,
|
||||||
|
store,
|
||||||
|
// appDg,
|
||||||
|
show,
|
||||||
|
showDg,
|
||||||
|
closeDg,
|
||||||
|
selectedObject,
|
||||||
|
vars: reactive(vars),
|
||||||
|
isSelected,
|
||||||
|
buttonsConfig: [
|
||||||
|
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
||||||
|
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
||||||
|
]
|
||||||
|
// filter
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.condition-object{
|
.condition-object {
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
max-height: 40px;
|
max-height: 40px;
|
||||||
padding: 2px;
|
margin: 0 2px;
|
||||||
}
|
}
|
||||||
.selected-obj{
|
|
||||||
margin: 0px;
|
.selected-obj {
|
||||||
|
margin: 0 2px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -66,18 +66,22 @@
|
|||||||
</div>
|
</div>
|
||||||
<!-- condition -->
|
<!-- condition -->
|
||||||
<div @click.stop @keypress.stop v-else >
|
<div @click.stop @keypress.stop v-else >
|
||||||
<div class="row no-wrap items-center">
|
<div class="row no-wrap items-center q-my-xs">
|
||||||
<ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"></ConditionObject>
|
<ConditionObject v-bind="prop.node" v-model="prop.node.object" :config="leftDynamicItemConfig" class="col-4"/>
|
||||||
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
|
<q-select v-model="prop.node.operator" :options="operators" class="operator" :outlined="true" :dense="true"></q-select>
|
||||||
<q-input v-if="!prop.node.object || !('options' in prop.node.object)"
|
<ConditionObject v-bind="prop.node" v-model="prop.node.value" :config="rightDynamicItemConfig" class="col-4"
|
||||||
|
:options="objectValueOptions(prop.node?.object?.options)"
|
||||||
|
/>
|
||||||
|
<!-- <ConditionObject v-bind="prop.node" v-model="prop.node.object" class="col-4"/> -->
|
||||||
|
<!-- <q-input v-if="!prop.node.object || !('options' in prop.node.object)"
|
||||||
v-model="prop.node.value"
|
v-model="prop.node.value"
|
||||||
class="condition-value" :outlined="true" :dense="true" ></q-input>
|
class="condition-value" :outlined="true" :dense="true" ></q-input> -->
|
||||||
<q-select v-if="prop.node.object && ('options' in prop.node.object)"
|
<!-- <q-select v-if="prop.node.object && ('options' in prop.node.object)"
|
||||||
v-model="prop.node.value"
|
v-model="prop.node.value"
|
||||||
:options="objectValueOptions(prop.node.object.options)"
|
:options="objectValueOptions(prop.node.object.options)"
|
||||||
clearable
|
clearable
|
||||||
value-key="index"
|
value-key="index"
|
||||||
class="condition-value" :outlined="true" :dense="true" ></q-select>
|
class="condition-value" :outlined="true" :dense="true" ></q-select> -->
|
||||||
<q-btn flat round dense icon="more_horiz" size="sm" >
|
<q-btn flat round dense icon="more_horiz" size="sm" >
|
||||||
<q-menu auto-close anchor="top right">
|
<q-menu auto-close anchor="top right">
|
||||||
<q-list>
|
<q-list>
|
||||||
@@ -113,9 +117,10 @@ import { finished } from 'stream';
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent,ref,reactive, computed } from 'vue';
|
import { defineComponent,ref,reactive, computed, inject } from 'vue';
|
||||||
import { INode,ConditionTree,GroupNode,ConditionNode, LogicalOperator,Operator,NodeType } from '../../types/Conditions';
|
import { INode,ConditionTree,GroupNode,ConditionNode, LogicalOperator,Operator,NodeType } from '../../types/Conditions';
|
||||||
import ConditionObject from './ConditionObject.vue';
|
import ConditionObject from './ConditionObject.vue';
|
||||||
|
import { IDynamicInputConfig } from 'src/types/ComponentTypes';
|
||||||
export default defineComponent( {
|
export default defineComponent( {
|
||||||
name: 'NodeCondition',
|
name: 'NodeCondition',
|
||||||
components: {
|
components: {
|
||||||
@@ -143,20 +148,18 @@ export default defineComponent( {
|
|||||||
return opts;
|
return opts;
|
||||||
});
|
});
|
||||||
|
|
||||||
const operators =computed(()=>{
|
const operatorSet = inject<Array<any>>('Operator')
|
||||||
const opts=[];
|
const operators = ref(operatorSet ? operatorSet : Object.values(Operator));
|
||||||
for(const op in Operator){
|
|
||||||
opts.push(Operator[op as keyof typeof Operator]);
|
|
||||||
}
|
|
||||||
return opts;
|
|
||||||
});
|
|
||||||
const tree = reactive(props.conditionTree);
|
const tree = reactive(props.conditionTree);
|
||||||
|
|
||||||
const conditionString = computed(()=>{
|
const conditionString = computed(()=>{
|
||||||
return tree.buildConditionString(tree.root);
|
return tree.buildConditionString(tree.root);
|
||||||
});
|
});
|
||||||
|
|
||||||
const objectValueOptions=(options:any):any[]=>{
|
const objectValueOptions=(options:any):any[]|null=>{
|
||||||
|
if(!options){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
const opts:any[] =[];
|
const opts:any[] =[];
|
||||||
Object.keys(options).forEach((key) =>
|
Object.keys(options).forEach((key) =>
|
||||||
{
|
{
|
||||||
@@ -223,11 +226,14 @@ export default defineComponent( {
|
|||||||
ticked.value=[];
|
ticked.value=[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const expanded=computed(()=>tree.getGroups(tree.root));
|
const expanded=computed(()=>tree.getGroups(tree.root));
|
||||||
// addCondition(tree.root);
|
// addCondition(tree.root);
|
||||||
|
const leftDynamicItemConfig = inject<IDynamicInputConfig>('leftDynamicItemConfig');
|
||||||
|
const rightDynamicItemConfig = inject<IDynamicInputConfig>('rightDynamicItemConfig');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
leftDynamicItemConfig,
|
||||||
|
rightDynamicItemConfig,
|
||||||
showingCondition,
|
showingCondition,
|
||||||
conditionString,
|
conditionString,
|
||||||
tree,
|
tree,
|
||||||
@@ -257,12 +263,14 @@ export default defineComponent( {
|
|||||||
.condition-value{
|
.condition-value{
|
||||||
min-width: 200px;
|
min-width: 200px;
|
||||||
max-height: 40px;
|
max-height: 40px;
|
||||||
padding: 2px;
|
margin: 0 2px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.operator{
|
.operator{
|
||||||
min-width: 150px;
|
min-width: 150px;
|
||||||
max-height: 40px;
|
max-height: 40px;
|
||||||
padding: 2px;
|
margin: 0 2px;
|
||||||
|
|
||||||
text-align: center;
|
text-align: center;
|
||||||
font-size: 12pt;
|
font-size: 12pt;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,7 @@
|
|||||||
|
|
||||||
<q-tab-panels v-model="tab" animated>
|
<q-tab-panels v-model="tab" animated>
|
||||||
<q-tab-panel name="fields">
|
<q-tab-panel name="fields">
|
||||||
<field-list v-model="selected" type="single" :filter="filter" :appId="appId"></field-list>
|
<field-list v-model="selected" type="single" :filter="filter" :appId="sourceApp ? sourceApp :appId " :fields="sourceFields"></field-list>
|
||||||
</q-tab-panel>
|
</q-tab-panel>
|
||||||
|
|
||||||
<q-tab-panel name="vars" >
|
<q-tab-panel name="vars" >
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ref, onMounted, reactive } from 'vue'
|
import { ref, onMounted, reactive, inject } from 'vue'
|
||||||
import FieldList from './FieldList.vue';
|
import FieldList from './FieldList.vue';
|
||||||
import VariableList from './VariableList.vue';
|
import VariableList from './VariableList.vue';
|
||||||
|
|
||||||
@@ -48,10 +48,14 @@ export default {
|
|||||||
filter:String
|
filter:String
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
|
const selected = ref([]);
|
||||||
|
console.log(selected);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
sourceFields : inject('sourceFields'),
|
||||||
|
sourceApp : inject('sourceApp'),
|
||||||
tab: ref('fields'),
|
tab: ref('fields'),
|
||||||
selected: ref([])
|
selected
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -5,7 +5,7 @@
|
|||||||
:url="uploadUrl"
|
:url="uploadUrl"
|
||||||
:label="title"
|
:label="title"
|
||||||
:headers="headers"
|
:headers="headers"
|
||||||
accept=".csv,.xlsx"
|
accept=".xlsx"
|
||||||
v-on:rejected="onRejected"
|
v-on:rejected="onRejected"
|
||||||
v-on:uploaded="onUploadFinished"
|
v-on:uploaded="onUploadFinished"
|
||||||
v-on:failed="onFailed"
|
v-on:failed="onFailed"
|
||||||
@@ -15,7 +15,7 @@
|
|||||||
|
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { createUploaderComponent, useQuasar } from 'quasar';
|
import { createUploaderComponent, useQuasar } from 'quasar';
|
||||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||||
import { ref } from 'vue';
|
import { ref } from 'vue';
|
||||||
const $q=useQuasar();
|
const $q=useQuasar();
|
||||||
@@ -30,7 +30,7 @@ import { ref } from 'vue';
|
|||||||
// https://quasar.dev/quasar-plugins/notify#Installation
|
// https://quasar.dev/quasar-plugins/notify#Installation
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
message: `CSVおよびExcelファイルを選択してください。`
|
message: `Excelファイルを選択してください。`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -40,7 +40,7 @@ import { ref } from 'vue';
|
|||||||
function onUploadFinished({xhr}:{xhr:XMLHttpRequest}){
|
function onUploadFinished({xhr}:{xhr:XMLHttpRequest}){
|
||||||
let msg="ファイルのアップロードが完了しました。";
|
let msg="ファイルのアップロードが完了しました。";
|
||||||
if(xhr && xhr.response){
|
if(xhr && xhr.response){
|
||||||
msg=`${msg} (${xhr.responseText})`;
|
msg=`${msg} (${xhr.responseText})`;
|
||||||
}
|
}
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'positive',
|
type: 'positive',
|
||||||
@@ -52,14 +52,28 @@ import { ref } from 'vue';
|
|||||||
}, 2000);
|
}, 2000);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 例外発生時、responseからエラー情報を取得する
|
||||||
|
* @param xhr
|
||||||
|
*/
|
||||||
|
function getResponseError(xhr:XMLHttpRequest){
|
||||||
|
try{
|
||||||
|
const resp = JSON.parse(xhr.responseText);
|
||||||
|
return 'detail' in resp ? resp.detail:'';
|
||||||
|
}catch(err){
|
||||||
|
return xhr.responseText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param info ファイルアップロード失敗時の処理
|
* @param info ファイルアップロード失敗時の処理
|
||||||
*/
|
*/
|
||||||
function onFailed({files,xhr}:{files: readonly any[],xhr:any}){
|
function onFailed({files,xhr}:{files: readonly any[],xhr:XMLHttpRequest}){
|
||||||
let msg ="ファイルアップロードが失敗しました。";
|
let msg ="ファイルアップロードが失敗しました。";
|
||||||
if(xhr && xhr.status){
|
if(xhr && xhr.status){
|
||||||
msg=`${msg} (${xhr.status }:${xhr.statusText})`
|
const detail = getResponseError(xhr);
|
||||||
|
msg=`${msg} (${xhr.status }:${detail})`
|
||||||
}
|
}
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type:"negative",
|
type:"negative",
|
||||||
@@ -74,7 +88,7 @@ import { ref } from 'vue';
|
|||||||
|
|
||||||
const headers = ref([{name:"Authorization",value:'Bearer ' + authStore.token}]);
|
const headers = ref([{name:"Authorization",value:'Bearer ' + authStore.token}]);
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
const props = withDefaults(defineProps<Props>(), {
|
||||||
title:"設計書から導入する(csv or excel)",
|
title:"設計書から導入する(Excel)",
|
||||||
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`
|
uploadUrl: `${process.env.KAB_BACKEND_URL}api/v1/createappfromexcel?format=1`
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<q-btn-dropdown
|
<q-btn-dropdown
|
||||||
color="primay"
|
class="customized-disabled-btn"
|
||||||
push
|
push
|
||||||
flat
|
flat
|
||||||
no-caps
|
no-caps
|
||||||
icon="share"
|
icon="share"
|
||||||
size="md"
|
size="md"
|
||||||
:label="userStore.currentDomain.domainName"
|
:label="userStore.currentDomain.domainName"
|
||||||
|
:disable-dropdown="isUnclickable"
|
||||||
|
:dropdown-icon="isUnclickable ? 'none' : ''"
|
||||||
|
:disable="isUnclickable"
|
||||||
>
|
>
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item v-for="domain in domains" :key="domain.domainName"
|
<q-item v-for="domain in domains" :key="domain.domainName"
|
||||||
@@ -26,18 +29,32 @@
|
|||||||
<script setup lang="ts" >
|
<script setup lang="ts" >
|
||||||
import { IDomainInfo } from 'src/types/ActionTypes';
|
import { IDomainInfo } from 'src/types/ActionTypes';
|
||||||
import { useAuthStore,IUserState } from 'stores/useAuthStore';
|
import { useAuthStore,IUserState } from 'stores/useAuthStore';
|
||||||
import { ref } from 'vue';
|
import { ref, computed } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
const userStore = useAuthStore();
|
const userStore = useAuthStore();
|
||||||
|
const route = useRoute()
|
||||||
const domains = ref<IDomainInfo[]>([]);
|
const domains = ref<IDomainInfo[]>([]);
|
||||||
(async ()=>{
|
(async ()=>{
|
||||||
domains.value = await userStore.getUserDomains();
|
domains.value = await userStore.getUserDomains();
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const isUnclickable = computed(()=>{
|
||||||
|
return route.path.startsWith('/FlowChart/') || domains.value === undefined || domains.value.length === 0;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
const onItemClick=(domain:IDomainInfo)=>{
|
const onItemClick=(domain:IDomainInfo)=>{
|
||||||
console.log(domain);
|
console.log(domain);
|
||||||
userStore.setCurrentDomain(domain);
|
userStore.setCurrentDomain(domain);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
|
.q-btn.disabled.customized-disabled-btn {
|
||||||
|
opacity: 1 !important;
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.q-btn.disabled.customized-disabled-btn * {
|
||||||
|
cursor: default !important;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
161
frontend/src/components/DynamicItemInput/DynamicItemInput.vue
Normal file
161
frontend/src/components/DynamicItemInput/DynamicItemInput.vue
Normal file
@@ -0,0 +1,161 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-mx-md" style="max-width: 600px;">
|
||||||
|
<!-- <q-card> -->
|
||||||
|
<div class="q-mb-md">
|
||||||
|
<q-input ref="inputRef" v-if="!optionsRef|| optionsRef.length===0"
|
||||||
|
outlined dense debounce="200" @update:model-value="updateSharedText"
|
||||||
|
v-model="sharedText" :readonly="!canInputFlag" autogrow>
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-btn flat round padding="none" icon="cancel" @click="clearSharedText" color="grey-6" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
<q-select v-if="optionsRef && optionsRef.length>0"
|
||||||
|
:model-value="sharedText"
|
||||||
|
:options="optionsRef"
|
||||||
|
clearable
|
||||||
|
value-key="index"
|
||||||
|
outlined
|
||||||
|
dense
|
||||||
|
use-input
|
||||||
|
hide-selected
|
||||||
|
input-debounce="10"
|
||||||
|
fill-input
|
||||||
|
@input-value="setValue"
|
||||||
|
@clear="sharedText=null"
|
||||||
|
hide-dropdown-icon
|
||||||
|
:readonly="!canInputFlag"
|
||||||
|
>
|
||||||
|
</q-select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row q-gutter-sm">
|
||||||
|
<q-btn v-for="button in buttonsConfig" :key="button.type" :color="button.color" @mousedown.prevent
|
||||||
|
@click="openDialog(button)" size="sm">
|
||||||
|
{{ button.label }}
|
||||||
|
</q-btn>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<show-dialog v-model:visible="dialogVisible" :name="currentDialogName" @close="closeDialog" min-width="400px">
|
||||||
|
<template v-slot:toolbar>
|
||||||
|
<q-input dense debounce="200" v-model="filter" clearable>
|
||||||
|
<template v-slot:before>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
<!-- asdf -->
|
||||||
|
<component :is="currentComponent" @select="handleSelect" :filter="filter" :appId="appId" />
|
||||||
|
</show-dialog>
|
||||||
|
<!-- </q-card> -->
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { ref, inject, watchEffect, defineComponent,PropType } from 'vue';
|
||||||
|
import FieldAdd from './FieldAdd.vue';
|
||||||
|
import VariableAdd from './VariableAdd.vue';
|
||||||
|
// import FunctionAdd from './FunctionAdd.vue';
|
||||||
|
import ShowDialog from '../ShowDialog.vue';
|
||||||
|
import { IButtonConfig } from 'src/types/ComponentTypes';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'DynamicItemInput',
|
||||||
|
components: {
|
||||||
|
FieldAdd,
|
||||||
|
VariableAdd,
|
||||||
|
// FunctionAdd,
|
||||||
|
ShowDialog
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
canInput: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
appId: {
|
||||||
|
type: String,
|
||||||
|
},
|
||||||
|
selectedObject: {
|
||||||
|
default: {}
|
||||||
|
},
|
||||||
|
options:{
|
||||||
|
type:Array as PropType< string[]>
|
||||||
|
},
|
||||||
|
buttonsConfig: {
|
||||||
|
type: Array as PropType<IButtonConfig[]>,
|
||||||
|
default: () => [
|
||||||
|
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const filter = ref('');
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
const currentDialogName = ref('');
|
||||||
|
const selectedObjectRef = ref(props.selectedObject);
|
||||||
|
const currentComponent = ref('FieldAdd');
|
||||||
|
const sharedText = ref(props.selectedObject?.sharedText ?? '');
|
||||||
|
const inputRef = ref();
|
||||||
|
const canInputFlag = ref(props.canInput);
|
||||||
|
const editable = ref(false);
|
||||||
|
|
||||||
|
const openDialog = (button: IButtonConfig) => {
|
||||||
|
currentDialogName.value = button.label;
|
||||||
|
currentComponent.value = button.type;
|
||||||
|
dialogVisible.value = true;
|
||||||
|
editable.value = canInputFlag.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDialog = () => {
|
||||||
|
dialogVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSelect = (value:any) => {
|
||||||
|
|
||||||
|
if (value && value._t && (value._t as string).length > 0) {
|
||||||
|
canInputFlag.value = editable.value;
|
||||||
|
}
|
||||||
|
selectedObjectRef.value={ sharedText: value._t, ...value };
|
||||||
|
sharedText.value = `${value._t}`;
|
||||||
|
// emit('update:selectedObject', { sharedText: sharedText.value, ...value });
|
||||||
|
dialogVisible.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const clearSharedText = () => {
|
||||||
|
sharedText.value = '';
|
||||||
|
selectedObjectRef.value={};
|
||||||
|
canInputFlag.value = true;
|
||||||
|
// emit('update:selectedObject', {});
|
||||||
|
}
|
||||||
|
const updateSharedText = (value:string) => {
|
||||||
|
sharedText.value = value;
|
||||||
|
selectedObjectRef.value= { sharedText: value,objectType:'text' }
|
||||||
|
// emit('update:selectedObject', { ...props.selectedObject, sharedText: value,objectType:'text' });
|
||||||
|
}
|
||||||
|
const setValue=(value:string)=>{
|
||||||
|
sharedText.value = value;
|
||||||
|
if(selectedObjectRef.value.sharedText!==value){
|
||||||
|
selectedObjectRef.value= { sharedText: value,objectType:'text' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const optionsRef=ref(props.options);
|
||||||
|
|
||||||
|
return {
|
||||||
|
filter,
|
||||||
|
dialogVisible,
|
||||||
|
currentDialogName,
|
||||||
|
currentComponent,
|
||||||
|
canInputFlag,
|
||||||
|
openDialog,
|
||||||
|
closeDialog,
|
||||||
|
handleSelect,
|
||||||
|
clearSharedText,
|
||||||
|
updateSharedText,
|
||||||
|
setValue,
|
||||||
|
sharedText,
|
||||||
|
inputRef,
|
||||||
|
optionsRef,
|
||||||
|
selectedObjectRef
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
41
frontend/src/components/DynamicItemInput/FieldAdd.vue
Normal file
41
frontend/src/components/DynamicItemInput/FieldAdd.vue
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<template>
|
||||||
|
<field-list v-model="selected" type="single" :filter="filter" :appId="sourceApp ? sourceApp : appId"
|
||||||
|
:fields="sourceFields" @update:modelValue="handleSelect" />
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, inject, ref } from 'vue';
|
||||||
|
import FieldList from '../FieldList.vue';
|
||||||
|
export default {
|
||||||
|
name: 'FieldAdd',
|
||||||
|
components: {
|
||||||
|
FieldList,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
appId: Number,
|
||||||
|
filter: String
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const sourceFields = inject<Array<unknown>>('sourceFields')
|
||||||
|
const sourceApp = inject<number>('sourceApp')
|
||||||
|
const appId = computed(() => {
|
||||||
|
if (sourceFields || sourceApp) {
|
||||||
|
return sourceApp.value
|
||||||
|
} else {
|
||||||
|
return props.appId
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
sourceFields,
|
||||||
|
sourceApp,
|
||||||
|
selected: ref([]),
|
||||||
|
handleSelect: (newSelection: any[]) => {
|
||||||
|
|
||||||
|
if (newSelection.length > 0) {
|
||||||
|
const v = newSelection[0]
|
||||||
|
emit('select', { _t: `field(${appId.value},${v.name})`, ...v }); // 假设您只需要选择的第一个字段的名称
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
42
frontend/src/components/DynamicItemInput/VariableAdd.vue
Normal file
42
frontend/src/components/DynamicItemInput/VariableAdd.vue
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
<template>
|
||||||
|
<variable-list v-model="selected" type="single" :vars="vars" :filter="filter" @update:modelValue="handleSelect" />
|
||||||
|
</template>
|
||||||
|
<script lang="ts">
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import VariableList from '../VariableList.vue';
|
||||||
|
import { useFlowEditorStore } from 'src/stores/flowEditor';
|
||||||
|
import { IActionVariable } from 'src/types/ActionTypes';
|
||||||
|
export default {
|
||||||
|
name: 'VariableAdd',
|
||||||
|
components: {
|
||||||
|
VariableList,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
appId: Number,
|
||||||
|
filter: String
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const store = useFlowEditorStore();
|
||||||
|
let vars: IActionVariable[] = [];
|
||||||
|
console.log(store.currentFlow !== undefined && store.activeNode !== undefined);
|
||||||
|
|
||||||
|
if (store.currentFlow !== undefined && store.activeNode !== undefined) {
|
||||||
|
vars = store.currentFlow.getVarNames(store.activeNode);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
vars,
|
||||||
|
selected: ref([]),
|
||||||
|
handleSelect: (newSelection: any[]) => {
|
||||||
|
if (newSelection.length > 0) {
|
||||||
|
const v = newSelection[0];
|
||||||
|
let name = v.name
|
||||||
|
if (typeof name === 'object') {
|
||||||
|
name = name.name
|
||||||
|
}
|
||||||
|
emit('select', { _t: `var(${name})`, ...v }); // 假设您只需要选择的第一个字段的名称
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -19,6 +19,7 @@
|
|||||||
</q-item-section>
|
</q-item-section>
|
||||||
</q-item>
|
</q-item>
|
||||||
<q-separator
|
<q-separator
|
||||||
|
class="q-my-sm"
|
||||||
v-if="isSeparator"
|
v-if="isSeparator"
|
||||||
inset
|
inset
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,56 +1,67 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md">
|
<div class="q-pa-md">
|
||||||
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type"
|
<q-table flat bordered :loading="!isLoaded" row-key="name" :selection="type" :selected="modelValue"
|
||||||
:selected="modelValue"
|
@update:selected="$emit('update:modelValue', $event)"
|
||||||
@update:selected="$emit('update:modelValue', $event)"
|
:filter="filter"
|
||||||
:filter="filter"
|
:columns="columns"
|
||||||
:columns="columns" :rows="rows" />
|
:rows="rows"
|
||||||
|
:pagination="pagination"
|
||||||
|
style="max-height: 55vh;"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script>
|
<script lang="ts">
|
||||||
import { ref, onMounted, reactive } from 'vue'
|
import { useAsyncState } from '@vueuse/core';
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
|
import { computed ,Prop,PropType,ref} from 'vue';
|
||||||
|
import {IField} from 'src/types/ComponentTypes';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'FieldList',
|
name: 'FieldList',
|
||||||
props: {
|
props: {
|
||||||
|
fields: Array as PropType<IField[]>,
|
||||||
name: String,
|
name: String,
|
||||||
type: String,
|
type: String,
|
||||||
appId: Number,
|
appId: Number,
|
||||||
modelValue:Array,
|
modelValue: Array,
|
||||||
filter:String
|
filter: String
|
||||||
},
|
},
|
||||||
emits:[
|
emits: [
|
||||||
'update:modelValue'
|
'update:modelValue'
|
||||||
],
|
],
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const isLoaded = ref(false);
|
// const rows = ref([]);
|
||||||
|
// const isLoaded = ref(false);
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: row => row.name, sortable: true },
|
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: 'name', sortable: true },
|
||||||
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
|
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
|
||||||
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
|
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
|
||||||
]
|
]
|
||||||
const rows = reactive([]);
|
|
||||||
onMounted(async () => {
|
|
||||||
const res = await api.get('api/v1/appfields', {
|
|
||||||
params: {
|
|
||||||
app: props.appId
|
|
||||||
}
|
|
||||||
});
|
|
||||||
let fields = res.data.properties;
|
|
||||||
console.log(fields);
|
|
||||||
Object.keys(fields).forEach((key) => {
|
|
||||||
const fld = fields[key];
|
|
||||||
rows.push({ name: fld.label, objectType: 'field', ...fld });
|
|
||||||
});
|
|
||||||
isLoaded.value = true;
|
|
||||||
});
|
|
||||||
|
|
||||||
|
const { state : rows, isReady: isLoaded, isLoading } = useAsyncState((args) => {
|
||||||
|
if (props.fields && Object.keys(props.fields).length > 0) {
|
||||||
|
return props.fields.map(f => ({ name: f.label, ...f ,objectType: 'field'}));
|
||||||
|
} else {
|
||||||
|
return api.get('api/v1/appfields', {
|
||||||
|
params: {
|
||||||
|
app: props.appId
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
const fields = res.data.properties;
|
||||||
|
return Object.values(fields).map((f:any) => ({ name: f.label, objectType: 'field', ...f }));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, [{ name: '', objectType: '', type: '', code: '', label: '' }])
|
||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
rows,
|
rows,
|
||||||
// selected: ref([]),
|
// selected: ref([]),
|
||||||
isLoaded
|
isLoaded,
|
||||||
|
pagination: ref({
|
||||||
|
rowsPerPage: 25,
|
||||||
|
sortBy: 'name',
|
||||||
|
descending: false,
|
||||||
|
page: 1,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,7 @@
|
|||||||
<div v-if="!isLoaded" class="spinner flex flex-center">
|
<div v-if="!isLoaded" class="spinner flex flex-center">
|
||||||
<q-spinner color="primary" size="3em" />
|
<q-spinner color="primary" size="3em" />
|
||||||
</div>
|
</div>
|
||||||
<q-table flat bordered v-else row-key="name" :selection="type" v-model:selected="selected" :columns="columns"
|
<q-table flat bordered v-else row-key="id" :selection="type" v-model:selected="selected" :columns="columns"
|
||||||
:rows="rows" :pagination="pageSetting" :filter="filter" style="max-height: 55vh;"/>
|
:rows="rows" :pagination="pageSetting" :filter="filter" style="max-height: 55vh;"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
import { ref, onMounted, reactive, watchEffect } from 'vue'
|
import { ref, onMounted, reactive, watchEffect } from 'vue'
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'fieldSelect',
|
name: 'fieldSelect',
|
||||||
props: {
|
props: {
|
||||||
@@ -25,13 +26,21 @@ export default {
|
|||||||
default: false,
|
default: false,
|
||||||
},
|
},
|
||||||
selectedFields:{
|
selectedFields:{
|
||||||
|
type:Array ,
|
||||||
|
default:()=>[]
|
||||||
|
},
|
||||||
|
fieldTypes:{
|
||||||
type:Array,
|
type:Array,
|
||||||
default:()=>[]
|
default:()=>[]
|
||||||
},
|
},
|
||||||
updateSelects: {
|
filter: String,
|
||||||
|
updateSelectFields: {
|
||||||
type: Function
|
type: Function
|
||||||
},
|
},
|
||||||
filter: String,
|
blackListLabel: {
|
||||||
|
type:Array,
|
||||||
|
default:()=>[]
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const isLoaded = ref(false);
|
const isLoaded = ref(false);
|
||||||
@@ -39,37 +48,52 @@ export default {
|
|||||||
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: row => row.name, sortable: true },
|
{ name: 'name', required: true, label: 'フィールド名', align: 'left', field: row => row.name, sortable: true },
|
||||||
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
|
{ name: 'code', label: 'フィールドコード', align: 'left', field: 'code', sortable: true },
|
||||||
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
|
{ name: 'type', label: 'フィールドタイプ', align: 'left', field: 'type', sortable: true }
|
||||||
]
|
];
|
||||||
const pageSetting = ref({
|
const pageSetting = ref({
|
||||||
sortBy: 'desc',
|
sortBy: 'name',
|
||||||
descending: false,
|
descending: false,
|
||||||
page: 2,
|
page: 1,
|
||||||
rowsPerPage: props.not_page ? 0 : 5
|
rowsPerPage: props.not_page ? 0 : 25
|
||||||
// rowsNumber: xx if getting data from a server
|
// rowsNumber: xx if getting data from a server
|
||||||
});
|
});
|
||||||
const rows = reactive([]);
|
const rows = reactive([]);
|
||||||
const selected = ref(props.selectedFields && props.selectedFields.length>0?props.selectedFields:[]);
|
const selected = ref((props.selectedFields && props.selectedFields.length>0)?props.selectedFields:[]);
|
||||||
|
|
||||||
watchEffect(() => {
|
|
||||||
props.updateSelects(selected);
|
|
||||||
});
|
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
const res = await api.get('api/v1/appfields', {
|
const url = props.fieldTypes.includes('SPACER')?'api/v1/allfields':'api/v1/appfields';
|
||||||
|
const res = await api.get(url, {
|
||||||
params: {
|
params: {
|
||||||
app: props.appId
|
app: props.appId
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
let fields = res.data.properties;
|
let fields = Object.values(res.data.properties);
|
||||||
console.log(fields);
|
for (const index in fields) {
|
||||||
Object.keys(fields).forEach((key) => {
|
const fld = fields[index]
|
||||||
const fld = fields[key];
|
if(props.blackListLabel.length > 0){
|
||||||
// rows.push({name:fields[key].label,code:fields[key].code,type:fields[key].type});
|
if(!props.blackListLabel.find(blackListItem => blackListItem === fld.label)){
|
||||||
rows.push({ name: fld.label, ...fld });
|
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
|
||||||
});
|
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
||||||
|
}else if(props.fieldTypes.includes("lookup") && ("lookup" in fld)){
|
||||||
|
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if(props.fieldTypes.length===0 || props.fieldTypes.includes(fld.type)){
|
||||||
|
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
||||||
|
}else if(props.fieldTypes.includes("lookup") && ("lookup" in fld)){
|
||||||
|
rows.push({id:index, name: fld.label || fld.code, ...fld });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
isLoaded.value = true;
|
isLoaded.value = true;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
watchEffect(()=>{
|
||||||
|
if (selected.value && selected.value[0] && props.updateSelectFields) {
|
||||||
|
props.updateSelectFields(selected)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
rows,
|
rows,
|
||||||
|
|||||||
@@ -1,20 +1,17 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- <div class="q-pa-md q-gutter-sm" > -->
|
<!-- <div class="q-pa-md q-gutter-sm" > -->
|
||||||
<q-dialog :model-value="visible" persistent bordered >
|
<q-dialog :model-value="visible" persistent bordered >
|
||||||
<q-card :style="cardStyle" style=" min-width: 40vw; max-width: 80vw; max-height: 95vh;">
|
<q-card class="" style="min-width: 40vw; max-width: 80vw; max-height: 95vh;" :style="cardStyle">
|
||||||
<q-toolbar class="bg-grey-4">
|
<q-toolbar class="bg-grey-4">
|
||||||
<q-toolbar-title>{{ name }}</q-toolbar-title>
|
<q-toolbar-title>{{ name }}</q-toolbar-title>
|
||||||
<q-space></q-space>
|
<q-space></q-space>
|
||||||
<slot name="toolbar"></slot>
|
<slot name="toolbar"></slot>
|
||||||
<q-btn flat round dense icon="close" @click="CloseDialogue('Cancel')" />
|
<q-btn flat round dense icon="close" @click="CloseDialogue('Cancel')" />
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
<q-card-section>
|
<q-card-section class="q-mt-md" :style="sectionStyle">
|
||||||
<!-- <div class="text-h6">{{ name }}</div> -->
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section class="q-pt-none" :style="sectionStyle">
|
|
||||||
<slot></slot>
|
<slot></slot>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-card-actions align="right" class="text-primary q-mt-lg">
|
<q-card-actions v-if="!disableBtn" align="right" class="text-primary">
|
||||||
<q-btn flat label="確定" v-close-popup @click="CloseDialogue('OK')" />
|
<q-btn flat label="確定" v-close-popup @click="CloseDialogue('OK')" />
|
||||||
<q-btn flat label="キャンセル" v-close-popup @click="CloseDialogue('Cancel')" />
|
<q-btn flat label="キャンセル" v-close-popup @click="CloseDialogue('Cancel')" />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
@@ -32,7 +29,11 @@ export default {
|
|||||||
width:String,
|
width:String,
|
||||||
height:String,
|
height:String,
|
||||||
minWidth:String,
|
minWidth:String,
|
||||||
minHeight:String
|
minHeight:String,
|
||||||
|
disableBtn:{
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
'close'
|
'close'
|
||||||
|
|||||||
36
frontend/src/components/UserList.vue
Normal file
36
frontend/src/components/UserList.vue
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<template>
|
||||||
|
<q-table :rows="rows" :columns="columns" row-key="id" :filter="props.filter" :loading="loading"
|
||||||
|
:pagination="pagination" selection="single" v-model:selected="selected"></q-table>
|
||||||
|
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
const props = defineProps<{filter:string}>()
|
||||||
|
const columns = [
|
||||||
|
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||||
|
{ name: 'firstName', label: '氏名', field: 'firstName', align: 'left', sortable: true },
|
||||||
|
{ name: 'lastName', label: '苗字', field: 'lastName', align: 'left', sortable: true },
|
||||||
|
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||||
|
];
|
||||||
|
|
||||||
|
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 10 });
|
||||||
|
const rows = ref([]);
|
||||||
|
const loading = ref(false);
|
||||||
|
const selected = ref([]);
|
||||||
|
defineExpose({
|
||||||
|
selected
|
||||||
|
})
|
||||||
|
const getUsers = async (filter = () => true) => {
|
||||||
|
loading.value = true;
|
||||||
|
const result = await api.get(`api/v1/users`);
|
||||||
|
rows.value = result.data.map((item) => {
|
||||||
|
return { id: item.id, firstName: item.first_name, lastName: item.last_name, email: item.email, isSuperuser: item.is_superuser, isActive: item.is_active }
|
||||||
|
}).filter(filter);
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getUsers();
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -1,43 +1,60 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md">
|
<div class="q-pa-md">
|
||||||
<q-table flat bordered row-key="name" :selection="type"
|
<q-table flat bordered row-key="id" :selection="type" :selected="modelValue" :filter="filter"
|
||||||
:selected="modelValue"
|
@update:selected="$emit('update:modelValue', $event)" :columns="columns" :rows="rows" />
|
||||||
@update:selected="$emit('update:modelValue', $event)"
|
|
||||||
:columns="columns" :rows="rows" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ref, reactive, PropType, compile } from 'vue';
|
import { PropType, reactive } from 'vue';
|
||||||
import {IActionNode,IActionVariable} from '../types/ActionTypes';
|
import { IActionVariable } from '../types/ActionTypes';
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: 'VariableList',
|
name: 'VariableList',
|
||||||
props: {
|
props: {
|
||||||
name: String,
|
name: String,
|
||||||
type: String,
|
type: String,
|
||||||
vars:{
|
vars: {
|
||||||
type:Array as PropType<IActionVariable[]>,
|
type: Array as PropType<IActionVariable[]>,
|
||||||
reqired:true,
|
reqired: true,
|
||||||
default:()=>[]
|
default: () => []
|
||||||
},
|
},
|
||||||
modelValue:Array
|
modelValue: Array,
|
||||||
|
filter: String
|
||||||
},
|
},
|
||||||
emits:[
|
emits: [
|
||||||
'update:modelValue'
|
'update:modelValue'
|
||||||
],
|
],
|
||||||
setup(props) {
|
setup(props) {
|
||||||
const columns= [
|
const variableName = (field) => {
|
||||||
{ name: 'actionName', label: 'アクション名',align: 'left',field: 'actionName',sortable: true},
|
const name = field.name;
|
||||||
{ name: 'displayName', label: '変数表示名', align: 'left',field: 'displayName', sortable: true },
|
return name.name;
|
||||||
{ name: 'name', label: '変数名', align: 'left',field: 'name',required: true, sortable: true }
|
}
|
||||||
|
const columns = [
|
||||||
|
{ name: 'actionName', label: 'アクション名', align: 'left', field: 'actionName', sortable: true },
|
||||||
|
{ name: 'displayName', label: '変数表示名', align: 'left', field: 'displayName', sortable: true },
|
||||||
|
{ name: 'name', label: '変数名', align: 'left', field: variableName, required: true, sortable: true }
|
||||||
];
|
];
|
||||||
const rows= props.vars.map((v)=>{
|
|
||||||
return {objectType:'variable',...v};
|
const rows = props.vars.flatMap((v) => {
|
||||||
|
if (v.name.vars && v.name.vars.length > 0) {
|
||||||
|
return v.name.vars
|
||||||
|
.filter(o => o.vName && o.logicalOperator && o.field)
|
||||||
|
.map(o => ({
|
||||||
|
id: uuidv4(),
|
||||||
|
objectType: 'variable',
|
||||||
|
name: { name: `${v.name.name}.${o.vName}` },
|
||||||
|
actionName: v.name.actionName,
|
||||||
|
displayName: v.name.displayName
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
return [{ objectType: 'variable', ...v }];
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
columns,
|
columns,
|
||||||
rows:reactive(rows)
|
rows: reactive(rows)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
frontend/src/components/dialog/VersionInput.vue
Normal file
39
frontend/src/components/dialog/VersionInput.vue
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<template>
|
||||||
|
<q-input
|
||||||
|
v-model="versionInfo.name"
|
||||||
|
filled
|
||||||
|
label="バージョン名"
|
||||||
|
:rules="[(val) => val.length <= 20 || '20字以内で入力ください']"
|
||||||
|
/>
|
||||||
|
<q-input
|
||||||
|
v-model="versionInfo.desc"
|
||||||
|
filled
|
||||||
|
type="textarea"
|
||||||
|
:rules="[(val) => val.length <= 80 || '80字以内で入力ください']"
|
||||||
|
label="説明"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, watch, defineProps, defineEmits } from 'vue';
|
||||||
|
import { QInput } from 'quasar';
|
||||||
|
import { IVersionInfo } from 'src/types/AppTypes';
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: IVersionInfo;
|
||||||
|
}>();
|
||||||
|
|
||||||
|
const versionInfo = ref({
|
||||||
|
...props.modelValue,
|
||||||
|
name: props.modelValue.name || `新バージョン ${new Date().toLocaleString()}`,
|
||||||
|
});
|
||||||
|
|
||||||
|
const emit = defineEmits(['update:modelValue']);
|
||||||
|
|
||||||
|
watch(
|
||||||
|
versionInfo,
|
||||||
|
() => {
|
||||||
|
emit('update:modelValue', { ...versionInfo.value });
|
||||||
|
},
|
||||||
|
{ immediate: true, deep: true }
|
||||||
|
);
|
||||||
|
</script>
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</template>
|
</template>
|
||||||
<AppSelect ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelect>
|
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelectBox>
|
||||||
</ShowDialog>
|
</ShowDialog>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -38,7 +38,7 @@
|
|||||||
import { defineComponent,ref } from 'vue';
|
import { defineComponent,ref } from 'vue';
|
||||||
import {AppInfo} from '../../types/ActionTypes'
|
import {AppInfo} from '../../types/ActionTypes'
|
||||||
import ShowDialog from '../../components/ShowDialog.vue';
|
import ShowDialog from '../../components/ShowDialog.vue';
|
||||||
import AppSelect from '../../components/AppSelect.vue';
|
import AppSelectBox from '../../components/AppSelectBox.vue';
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
import { useAuthStore } from 'src/stores/useAuthStore';
|
import { useAuthStore } from 'src/stores/useAuthStore';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -47,7 +47,7 @@ export default defineComponent({
|
|||||||
"appSelected"
|
"appSelected"
|
||||||
],
|
],
|
||||||
components:{
|
components:{
|
||||||
AppSelect,
|
AppSelectBox,
|
||||||
ShowDialog
|
ShowDialog
|
||||||
},
|
},
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
|
|||||||
@@ -1,48 +1,47 @@
|
|||||||
<template>
|
<template>
|
||||||
<!-- <div class="q-pa-md q-gutter-sm"> -->
|
<!-- <div class="q-pa-md q-gutter-sm"> -->
|
||||||
<q-tree
|
<q-tree :nodes="store.eventTree.screens" node-key="eventId" children-key="events" no-connectors
|
||||||
:nodes="store.eventTree.screens"
|
v-model:expanded="store.expandedScreen" :dense="true" :ref="tree">
|
||||||
node-key="eventId"
|
<template v-slot:header-EVENT="prop">
|
||||||
children-key="events"
|
<div :ref="prop.node.eventId" class="row col items-center no-wrap event-node" @click="onSelected(prop.node)">
|
||||||
no-connectors
|
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px"
|
||||||
v-model:expanded="store.expandedScreen"
|
class="q-mr-sm">
|
||||||
:dense="true"
|
</q-icon>
|
||||||
:ref="tree"
|
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div>
|
||||||
>
|
<q-space></q-space>
|
||||||
<template v-slot:header-EVENT="prop">
|
<!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> -->
|
||||||
<div class="row col items-start no-wrap event-node" @click="onSelected(prop.node)">
|
</div>
|
||||||
<q-icon v-if="prop.node.eventId"
|
</template>
|
||||||
name="play_circle"
|
<template v-slot:header-CHANGE="prop">
|
||||||
:color="prop.node.hasFlow?'green':'grey'"
|
<div class="row col items-start no-wrap event-node">
|
||||||
size="16px" class="q-mr-sm">
|
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div>
|
||||||
</q-icon>
|
<q-space></q-space>
|
||||||
<div class="no-wrap" :class="selectedEvent && prop.node.eventId===selectedEvent.eventId?'selected-node':''">{{ prop.node.label }}</div>
|
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm"
|
||||||
|
@click="addChangeEvent(prop.node)"></q-icon>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
<template v-slot:header-DELETABLE="prop">
|
||||||
|
<div class="row col items-start event-node" @click="onSelected(prop.node)">
|
||||||
|
<q-icon v-if="prop.node.eventId" name="play_circle" :color="prop.node.hasFlow ? 'green' : 'grey'" size="16px" class="q-mr-sm" />
|
||||||
|
<div class="no-wrap" :class="getSelectedClass(prop.node)">{{ prop.node.label }}</div>
|
||||||
<q-space></q-space>
|
<q-space></q-space>
|
||||||
<!-- <q-icon v-if="prop.node.hasFlow" name="delete" color="negative" size="16px" class="q-mr-sm"></q-icon> -->
|
<q-icon name="delete_forever" color="negative" size="16px" @click="deleteEvent(prop.node)"></q-icon>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template v-slot:header-CHANGE="prop" >
|
</q-tree>
|
||||||
<div class="row col items-start no-wrap event-node" >
|
<show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg">
|
||||||
<div class="no-wrap">{{ prop.node.label }}</div>
|
<field-select ref="appDg" name="フィールド" type="single" :fieldTypes="fieldTypes" :appId="store.appInfo?.appId"></field-select>
|
||||||
<q-space></q-space>
|
</show-dialog>
|
||||||
<q-icon name="add_circle" color="primary" size="16px" class="q-mr-sm" @click="addChangeEvent(prop.node)"></q-icon>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</q-tree>
|
|
||||||
<show-dialog v-model:visible="showDialog" name="フィールド選択" @close="closeDg" widht="400px">
|
|
||||||
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
|
|
||||||
</show-dialog>
|
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, computed, ref } from 'vue';
|
import { QTree, useQuasar } from 'quasar';
|
||||||
import { IKintoneEvent ,IKintoneEventGroup, IKintoneEventNode, kintoneEvent} from '../../types/KintoneEvents';
|
import { ActionFlow, RootAction } from 'src/types/ActionTypes';
|
||||||
import { storeToRefs } from 'pinia';
|
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
import { ActionFlow, ActionNode, RootAction } from 'src/types/ActionTypes';
|
import { defineComponent, ref, watchEffect } from 'vue';
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
import { IKintoneEvent, IKintoneEventGroup, IKintoneEventNode, kintoneEvent } from '../../types/KintoneEvents';
|
||||||
import FieldSelect from '../FieldSelect.vue';
|
import FieldSelect from '../FieldSelect.vue';
|
||||||
import { QTree } from 'quasar';
|
import ShowDialog from '../ShowDialog.vue';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'EventTree',
|
name: 'EventTree',
|
||||||
components: {
|
components: {
|
||||||
@@ -50,71 +49,110 @@ export default defineComponent({
|
|||||||
FieldSelect,
|
FieldSelect,
|
||||||
},
|
},
|
||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
|
const $q = useQuasar();
|
||||||
const appDg = ref();
|
const appDg = ref();
|
||||||
const store = useFlowEditorStore();
|
const store = useFlowEditorStore();
|
||||||
const showDialog = ref(false);
|
const showDialog = ref(false);
|
||||||
const tree = ref<QTree>();
|
const tree = ref<QTree>();
|
||||||
|
const fieldTypes=[
|
||||||
|
'RADIO_BUTTON',
|
||||||
|
'DROP_DOWN',
|
||||||
|
'CHECK_BOX',
|
||||||
|
'MULTI_SELECT',
|
||||||
|
'USER_SELECT',
|
||||||
|
'GROUP_SELECT',
|
||||||
|
'ORGANIZATION_SELECT',
|
||||||
|
'DATE',
|
||||||
|
'DATETIME',
|
||||||
|
'TIME',
|
||||||
|
'SINGLE_LINE_TEXT',
|
||||||
|
'NUMBER'];
|
||||||
// const eventTree=ref(kintoneEvents);
|
// const eventTree=ref(kintoneEvents);
|
||||||
// const selectedFlow = store.currentFlow;
|
// const selectedFlow = store.currentFlow;
|
||||||
|
|
||||||
// const expanded=ref();
|
// const expanded=ref();
|
||||||
const selectedEvent = ref<IKintoneEvent|null>(null);
|
const selectedEvent = ref<IKintoneEvent | undefined>(store.selectedEvent);
|
||||||
const selectedChangeEvent=ref<IKintoneEventGroup|null>(null);
|
const selectedChangeEvent = ref<IKintoneEventGroup | undefined>(undefined);
|
||||||
const isFieldChange = (node:IKintoneEventNode)=>{
|
const isFieldChange = (node: IKintoneEventNode) => {
|
||||||
return node.header=='EVENT' && node.eventId.indexOf(".change.")>-1;
|
return node.header == 'EVENT' && node.eventId.indexOf(".change.") > -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getSelectedClass = (node: IKintoneEventNode) => {
|
||||||
|
return store.selectedEvent && node.eventId === store.selectedEvent.eventId ? 'selected-node' : '';
|
||||||
|
};
|
||||||
|
|
||||||
//フィールド値変更イベント追加
|
//フィールド値変更イベント追加
|
||||||
const closeDg = (val:string) => {
|
const closeDg = (val: string) => {
|
||||||
if (val == 'OK') {
|
if (val == 'OK') {
|
||||||
if(!selectedChangeEvent.value){return;}
|
if (!selectedChangeEvent.value) { return; }
|
||||||
const field = appDg.value.selected[0];
|
const field = appDg.value.selected[0];
|
||||||
const eventid = `${selectedChangeEvent.value.eventId}.${field.code}`;
|
const eventid = `${selectedChangeEvent.value.eventId}.${field.code}`;
|
||||||
if(store.eventTree.findEventById(eventid)){
|
if (store.eventTree.findEventById(eventid)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
selectedChangeEvent.value?.events.push(
|
selectedChangeEvent.value?.events.push(new kintoneEvent(
|
||||||
new kintoneEvent(
|
field.name,
|
||||||
field.label,
|
eventid,
|
||||||
eventid,
|
selectedChangeEvent.value.eventId,
|
||||||
selectedChangeEvent.value.eventId)
|
'DELETABLE'
|
||||||
);
|
));
|
||||||
tree.value?.expanded?.push(selectedChangeEvent.value.eventId);
|
tree.value?.expanded?.push(selectedChangeEvent.value.eventId);
|
||||||
tree.value?.expandAll();
|
tree.value?.expandAll();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const addChangeEvent=(node:IKintoneEventGroup)=>{
|
const addChangeEvent = (node: IKintoneEventGroup) => {
|
||||||
if(store.appInfo===undefined){
|
if (store.appInfo === undefined) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
selectedChangeEvent.value=node;
|
selectedChangeEvent.value = node;
|
||||||
showDialog.value=true;
|
showDialog.value = true;
|
||||||
}
|
}
|
||||||
const onSelected=(node:IKintoneEvent)=>{
|
|
||||||
if(!node.eventId){
|
const deleteEvent = (node: IKintoneEvent) => {
|
||||||
return;
|
if (!node.eventId) {
|
||||||
}
|
return;
|
||||||
selectedEvent.value=node;
|
}
|
||||||
if(store.appInfo===undefined){
|
store.deleteEvent(node);
|
||||||
return;
|
store.selectFlow(undefined)
|
||||||
}
|
|
||||||
const screen = store.eventTree.findEventById(node.parentId);
|
$q.notify({
|
||||||
let flow =store.findFlowByEventId(node.eventId);
|
type: 'positive',
|
||||||
let screenName=screen!==null?screen.label:"";
|
caption: "通知",
|
||||||
let nodeLabel = node.label;
|
message: `イベント ${node.label} 削除`
|
||||||
// if(isFieldChange(node)){
|
})
|
||||||
// screenName=nodeLabel;
|
}
|
||||||
// nodeLabel=`${node.label}の値を変更したとき`;
|
|
||||||
// }
|
const onSelected = (node: IKintoneEvent) => {
|
||||||
if(flow!==undefined && flow!==null ){
|
if (!node.eventId) {
|
||||||
store.selectFlow(flow);
|
return;
|
||||||
}else{
|
}
|
||||||
const root = new RootAction(node.eventId,screenName,nodeLabel)
|
selectedEvent.value = node;
|
||||||
const flow =new ActionFlow(root);
|
if (store.appInfo === undefined) {
|
||||||
store.flows?.push(flow);
|
return;
|
||||||
store.selectFlow(flow);
|
}
|
||||||
selectedEvent.value.flowData=flow;
|
const screen = store.eventTree.findEventById(node.parentId);
|
||||||
}
|
|
||||||
|
let flow = store.findFlowByEventId(node.eventId);
|
||||||
|
let screenName = screen !== null ? screen.label : '';
|
||||||
|
let nodeLabel = node.label;
|
||||||
|
// if(isFieldChange(node)){
|
||||||
|
// screenName=nodeLabel;
|
||||||
|
// nodeLabel=`${node.label}の値を変更したとき`;
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (flow !== undefined && flow !== null) {
|
||||||
|
store.selectFlow(flow);
|
||||||
|
} else {
|
||||||
|
const root = new RootAction(node.eventId, screenName, nodeLabel)
|
||||||
|
const flow = new ActionFlow(root);
|
||||||
|
store.flows?.push(flow);
|
||||||
|
store.selectFlow(flow);
|
||||||
|
selectedEvent.value.flowData = flow;
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
watchEffect(()=>{
|
||||||
|
store.setCurrentEvent(selectedEvent.value);
|
||||||
|
});
|
||||||
return {
|
return {
|
||||||
// eventTree,
|
// eventTree,
|
||||||
// expanded,
|
// expanded,
|
||||||
@@ -122,30 +160,38 @@ export default defineComponent({
|
|||||||
tree,
|
tree,
|
||||||
showDialog,
|
showDialog,
|
||||||
isFieldChange,
|
isFieldChange,
|
||||||
|
getSelectedClass,
|
||||||
onSelected,
|
onSelected,
|
||||||
selectedEvent,
|
selectedEvent,
|
||||||
addChangeEvent,
|
addChangeEvent,
|
||||||
|
deleteEvent,
|
||||||
closeDg,
|
closeDg,
|
||||||
store
|
store,
|
||||||
|
fieldTypes
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.nowrap{
|
.nowrap {
|
||||||
flex-wrap:nowarp;
|
flex-wrap: nowarp;
|
||||||
text-wrap:nowarp;
|
text-wrap: nowarp;
|
||||||
}
|
}
|
||||||
.event-node{
|
|
||||||
cursor:pointer;
|
.event-node {
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
.selected-node{
|
|
||||||
|
.selected-node {
|
||||||
color: $primary;
|
color: $primary;
|
||||||
font-weight: bolder;
|
font-weight: bolder;
|
||||||
}
|
}
|
||||||
.event-node:hover{
|
|
||||||
|
.event-node:hover {
|
||||||
background-color: $light-blue-1;
|
background-color: $light-blue-1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.delete-btn {
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
|
|||||||
@@ -205,7 +205,7 @@ export default defineComponent({
|
|||||||
*/
|
*/
|
||||||
const varName =(node:IActionNode)=>{
|
const varName =(node:IActionNode)=>{
|
||||||
const prop = node.actionProps.find((prop) => prop.props.name === "verName");
|
const prop = node.actionProps.find((prop) => prop.props.name === "verName");
|
||||||
return prop?.props.modelValue;
|
return prop?.props.modelValue.name;
|
||||||
};
|
};
|
||||||
const copyFlow=()=>{
|
const copyFlow=()=>{
|
||||||
context.emit('copyFlow', props.actionNode);
|
context.emit('copyFlow', props.actionNode);
|
||||||
|
|||||||
@@ -1,235 +1,165 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-my-md" v-bind="$attrs">
|
<div class="q-my-md" v-bind="$attrs">
|
||||||
<q-card flat>
|
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
|
||||||
<q-card-section class="q-pa-none q-my-sm q-mr-md">
|
:rules="rulesExp" lazy-rules="ondemand" @clear="clear" ref="fieldRef">
|
||||||
<!-- <div class=" q-my-none ">App Field Select</div> -->
|
<template v-slot:control>
|
||||||
<div class="row q-mb-xs">
|
{{ isSelected ? selectedField.app?.name : "(未選択)" }}
|
||||||
<div class="text-primary q-mb-xs text-caption">{{ $props.displayName }}</div>
|
</template>
|
||||||
</div>
|
<template v-slot:hint v-if="!isSelected">
|
||||||
<div class="row">
|
{{ placeholder }}
|
||||||
<div class="col">
|
</template>
|
||||||
<div class="q-mb-xs">{{ selectedField.app?.name || '未選択' }}</div>
|
<template v-slot:append>
|
||||||
</div>
|
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
|
||||||
<div class="col-1">
|
</template>
|
||||||
<q-btn round flat size="sm" color="primary" icon="search" @click="showDg" />
|
</q-field>
|
||||||
</div>
|
<div v-if="selectedField.fields && selectedField.fields.length > 0">
|
||||||
</div>
|
<q-list bordered>
|
||||||
</q-card-section>
|
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator v-slot="{ item, index }">
|
||||||
<q-separator />
|
<q-item :key="index" dense clickable>
|
||||||
<q-card-section class="q-pa-none q-ma-none">
|
<q-item-section>
|
||||||
<div style="">
|
<q-item-label>
|
||||||
<div v-if="selectedField.fields && selectedField.fields.length > 0 ">
|
{{ item.label }}
|
||||||
<q-list bordered>
|
</q-item-label>
|
||||||
<q-virtual-scroll style="max-height: 160px;" :items="selectedField.fields" separator v-slot="{ item, index }">
|
</q-item-section>
|
||||||
<q-item :key="index" dense clickable >
|
<q-item-section side>
|
||||||
<q-item-section>
|
<q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
|
||||||
<q-item-label>
|
</q-item-section>
|
||||||
{{ item.label }}
|
</q-item>
|
||||||
</q-item-label>
|
</q-virtual-scroll>
|
||||||
</q-item-section>
|
</q-list>
|
||||||
<q-item-section side>
|
</div>
|
||||||
<q-btn round flat size="sm" icon="clear" @click="removeField(index)" />
|
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeAFBox">
|
||||||
</q-item-section>
|
<AppFieldSelectBox v-model:selectedField="selectedField" :selectType="selectType" ref="afBox"
|
||||||
</q-item>
|
:fieldTypes="fieldTypes" />
|
||||||
</q-virtual-scroll>
|
|
||||||
</q-list>
|
|
||||||
</div>
|
|
||||||
<!-- <div v-else class="row q-mt-lg">
|
|
||||||
</div> -->
|
|
||||||
</div>
|
|
||||||
<!-- <q-separator /> -->
|
|
||||||
</q-card-section>
|
|
||||||
<q-card-section class="q-px-none q-py-xs" v-if="selectedField.fields && selectedField.fields.length===0">
|
|
||||||
<div class="row">
|
|
||||||
<div class="text-grey text-caption"> {{ $props.placeholder }}</div>
|
|
||||||
<!-- <q-btn flat color="grey" label="clear" @click="clear" /> -->
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
|
||||||
</q-card>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeFieldDialog" ref="fieldDlg">
|
|
||||||
|
|
||||||
<div class="q-mx-md q-mb-lg">
|
|
||||||
<div class="q-mb-xs q-ml-md text-primary">アプリ選択</div>
|
|
||||||
|
|
||||||
<div class="q-pa-md row" style="border: 1px solid rgba(0, 0, 0, 0.12); border-radius: 4px;">
|
|
||||||
<div v-if="!showSelectApp && selectedField.app">{{ selectedField.app?.name }}</div>
|
|
||||||
<q-space />
|
|
||||||
<div>
|
|
||||||
<q-btn outline dense label="選 択" padding="none sm" color="primary" @click="() => {
|
|
||||||
showSelectApp = true;
|
|
||||||
}"></q-btn>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="!showSelectApp && selectedField.app?.name">
|
|
||||||
<div>
|
|
||||||
<div class="row q-mb-md">
|
|
||||||
<!-- <div class="col"> -->
|
|
||||||
<div class="q-mb-xs q-ml-md text-primary">フィールド選択</div>
|
|
||||||
<!-- </div> -->
|
|
||||||
<q-space />
|
|
||||||
<!-- <div class="col"> -->
|
|
||||||
<div class="q-mr-md">
|
|
||||||
<q-input dense debounce="300" v-model="fieldFilter" placeholder="フィールド検索" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="row">
|
|
||||||
<field-select ref="fieldDlg" name="フィールド" :type="selectType" :updateSelects="updateItems"
|
|
||||||
:appId="selectedField.app?.id" not_page :filter="fieldFilter" :selectedFields="selectedField.fields"></field-select>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div style="min-width: 45vw;" v-else>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</show-dialog>
|
|
||||||
|
|
||||||
<show-dialog v-model:visible="showSelectApp" name="アプリ選択" @close="closeAppDlg">
|
|
||||||
<template v-slot:toolbar>
|
|
||||||
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
|
||||||
<template v-slot:before>
|
|
||||||
<q-icon name="search" />
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<AppSelect ref="appDlg" name="アプリ" type="single" :filter="filter"
|
|
||||||
:updateExternalSelectAppInfo="updateExternalSelectAppInfo"></AppSelect>
|
|
||||||
</show-dialog>
|
</show-dialog>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref, watchEffect, computed } from 'vue';
|
import { computed, defineComponent, ref, watchEffect } from 'vue';
|
||||||
|
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
||||||
import ShowDialog from '../ShowDialog.vue';
|
import ShowDialog from '../ShowDialog.vue';
|
||||||
import FieldSelect from '../FieldSelect.vue';
|
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
import AppSelect from '../AppSelect.vue';
|
|
||||||
interface IApp{
|
export interface IApp {
|
||||||
id:string,
|
id: string,
|
||||||
name:string
|
name: string
|
||||||
}
|
}
|
||||||
interface IField {
|
export interface IField {
|
||||||
name: string,
|
name: string,
|
||||||
code: string,
|
code: string,
|
||||||
type: string
|
type: string,
|
||||||
|
label?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface IAppFields{
|
export interface IAppFields {
|
||||||
app?:IApp,
|
app?: IApp,
|
||||||
fields:IField[]
|
fields: IField[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
inheritAttrs:false,
|
inheritAttrs: false,
|
||||||
name: 'FieldInput',
|
name: 'AppFieldSelect2',
|
||||||
components: {
|
components: {
|
||||||
ShowDialog,
|
ShowDialog,
|
||||||
FieldSelect,
|
AppFieldSelectBox
|
||||||
AppSelect,
|
},
|
||||||
|
props: {
|
||||||
|
displayName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
},
|
},
|
||||||
props: {
|
name: {
|
||||||
displayName: {
|
type: String,
|
||||||
type: String,
|
default: '',
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: Object,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
selectType:{
|
|
||||||
type:String,
|
|
||||||
default:'single'
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
placeholder: {
|
||||||
const appDlg = ref();
|
type: String,
|
||||||
const fieldDlg = ref();
|
default: '',
|
||||||
const show = ref(false);
|
},
|
||||||
const showSelectApp = ref(false);
|
modelValue: {
|
||||||
const selectedField = ref<IAppFields>({
|
type: Object,
|
||||||
app:undefined,
|
default: null
|
||||||
fields:[]
|
},
|
||||||
});
|
selectType: {
|
||||||
if(props.modelValue && "app" in props.modelValue && "fields" in props.modelValue){
|
type: String,
|
||||||
selectedField.value=props.modelValue as IAppFields;
|
default: 'single'
|
||||||
}
|
},
|
||||||
const store = useFlowEditorStore();
|
fieldTypes: {
|
||||||
|
type: Array,
|
||||||
const isSelected = computed(() => {
|
default: () => []
|
||||||
return selectedField.value !== null && typeof selectedField.value === 'object' && ('app' in selectedField.value)
|
},
|
||||||
});
|
//例:[val=>!!val ||'入力してください']
|
||||||
|
rules: {
|
||||||
const showDg = () => {
|
type: String,
|
||||||
show.value = true;
|
default: undefined
|
||||||
};
|
},
|
||||||
|
required: {
|
||||||
const clear = () => {
|
type: Boolean,
|
||||||
selectedField.value ={
|
default: false
|
||||||
fields:[]
|
},
|
||||||
} ;
|
requiredMessage: {
|
||||||
}
|
type: String,
|
||||||
|
default: ''
|
||||||
const closeAppDlg = (val: string) => {
|
|
||||||
if (val == 'OK') {
|
|
||||||
selectedField.value.app = appDlg.value.selected[0];
|
|
||||||
selectedField.value.fields=[];
|
|
||||||
showSelectApp.value=false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const closeFieldDialog=(val:string)=>{
|
|
||||||
if (val == 'OK') {
|
|
||||||
selectedField.value.fields = fieldDlg.value.selected;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
const updateExternalSelectAppInfo = (newAppinfo:IApp) => {
|
|
||||||
// selectedField.value.app = newAppinfo
|
|
||||||
}
|
|
||||||
|
|
||||||
const updateItems = (newFields:IField[]) => {
|
|
||||||
// selectedField.value.fields = newFields
|
|
||||||
}
|
|
||||||
|
|
||||||
const removeField=(index:number)=>{
|
|
||||||
selectedField.value.fields.splice(index,1);
|
|
||||||
}
|
|
||||||
watchEffect(() => {
|
|
||||||
emit('update:modelValue', selectedField.value);
|
|
||||||
});
|
|
||||||
|
|
||||||
return {
|
|
||||||
store,
|
|
||||||
appDlg,
|
|
||||||
fieldDlg,
|
|
||||||
show,
|
|
||||||
showDg,
|
|
||||||
closeAppDlg,
|
|
||||||
closeFieldDialog,
|
|
||||||
selectedField,
|
|
||||||
showSelectApp,
|
|
||||||
isSelected,
|
|
||||||
updateExternalSelectAppInfo,
|
|
||||||
filter: ref(),
|
|
||||||
updateItems,
|
|
||||||
clear,
|
|
||||||
fieldFilter: ref(),
|
|
||||||
removeField
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const show = ref(false);
|
||||||
|
const afBox = ref();
|
||||||
|
const fieldRef = ref();
|
||||||
|
const selectedField = ref<IAppFields>({
|
||||||
|
app: undefined,
|
||||||
|
fields: []
|
||||||
|
});
|
||||||
|
if (props.modelValue && 'app' in props.modelValue && 'fields' in props.modelValue) {
|
||||||
|
selectedField.value = props.modelValue as IAppFields;
|
||||||
|
}
|
||||||
|
const store = useFlowEditorStore();
|
||||||
|
|
||||||
|
const clear = () => {
|
||||||
|
selectedField.value = {
|
||||||
|
fields: []
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeField = (index: number) => {
|
||||||
|
selectedField.value.fields.splice(index, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeAFBox = (val: string) => {
|
||||||
|
if (val == 'OK') {
|
||||||
|
console.log(afBox.value);
|
||||||
|
selectedField.value = afBox.value.selField;
|
||||||
|
fieldRef.value.validate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isSelected = computed(() => {
|
||||||
|
return !!selectedField.value.app
|
||||||
|
});
|
||||||
|
|
||||||
|
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||||
|
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||||
|
const requiredExp = props.required ? [((val: any) => (val && val.app && val.fields && val.fields.length > 0) || errmsg)] : [];
|
||||||
|
const rulesExp = [...requiredExp, ...customExp];
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
emit('update:modelValue', selectedField.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
store,
|
||||||
|
afBox,
|
||||||
|
show,
|
||||||
|
showDg: () => { show.value = true },
|
||||||
|
selectedField,
|
||||||
|
clear,
|
||||||
|
removeField,
|
||||||
|
closeAFBox,
|
||||||
|
isSelected,
|
||||||
|
rulesExp,
|
||||||
|
fieldRef
|
||||||
|
};
|
||||||
|
}
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
112
frontend/src/components/right/AppSelect.vue
Normal file
112
frontend/src/components/right/AppSelect.vue
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<template>
|
||||||
|
<div v-bind="$attrs">
|
||||||
|
<q-field :label="displayName" labelColor="primary" stack-label
|
||||||
|
:rules="rulesExp"
|
||||||
|
lazy-rules="ondemand"
|
||||||
|
v-model="selectedApp"
|
||||||
|
ref="fieldRef">
|
||||||
|
<template v-slot:control>
|
||||||
|
<q-card flat class="full-width">
|
||||||
|
<q-card-actions vertical>
|
||||||
|
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">アプリ選択</q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
<q-card-section class="text-caption">
|
||||||
|
<div v-if="selectedApp.app.name">
|
||||||
|
{{ selectedApp.app.name }}
|
||||||
|
</div>
|
||||||
|
<div v-else>{{ placeholder }}</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</template>
|
||||||
|
</q-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ShowDialog v-model:visible="dgIsShow" name="アプリ選択" @close="closeDg" min-width="50vw" min-height="50vh">
|
||||||
|
<template v-slot:toolbar>
|
||||||
|
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
||||||
|
<template v-slot:before>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
<AppSelectBox ref="appDg" name="アプリ" type="single" :filter="filter"></AppSelectBox>
|
||||||
|
</ShowDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { computed, defineComponent, reactive, ref, watchEffect } from 'vue';
|
||||||
|
import ShowDialog from '../ShowDialog.vue';
|
||||||
|
import AppSelectBox from '../AppSelectBox.vue';
|
||||||
|
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
inheritAttrs: false,
|
||||||
|
name: 'AppSelect',
|
||||||
|
components: {
|
||||||
|
ShowDialog,
|
||||||
|
AppSelectBox
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
displayName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
//例:[val=>!!val ||'入力してください']
|
||||||
|
rules: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
requiredMessage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const appDg = ref();
|
||||||
|
const fieldRef=ref();
|
||||||
|
const dgIsShow = ref(false)
|
||||||
|
const selectedApp = props.modelValue && props.modelValue.app ? props.modelValue : reactive({app:{}});
|
||||||
|
const closeDg = (state: string) => {
|
||||||
|
dgIsShow.value = false;
|
||||||
|
if (state == 'OK') {
|
||||||
|
selectedApp.app = appDg.value.selected[0];
|
||||||
|
fieldRef.value.validate();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//ルール設定
|
||||||
|
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||||
|
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
|
||||||
|
const requiredExp = props.required ? [((val: any) => (!!val && !!val.app && !!val.app.name) || errmsg)] : [];
|
||||||
|
const rulesExp = [...requiredExp, ...customExp];
|
||||||
|
watchEffect(() => {
|
||||||
|
emit('update:modelValue', selectedApp);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
filter: ref(''),
|
||||||
|
dgIsShow,
|
||||||
|
appDg,
|
||||||
|
fieldRef,
|
||||||
|
closeDg,
|
||||||
|
selectedApp,
|
||||||
|
rulesExp
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
133
frontend/src/components/right/CascadingDropDown.vue
Normal file
133
frontend/src/components/right/CascadingDropDown.vue
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
<template>
|
||||||
|
<div v-bind="$attrs">
|
||||||
|
<q-field :label="displayName" labelColor="primary" stack-label lazy-rules="ondemand" ref="fieldRef">
|
||||||
|
<template v-slot:control>
|
||||||
|
<q-card flat class="full-width">
|
||||||
|
<q-card-actions vertical>
|
||||||
|
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">クリックで設定</q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
<q-card-section class="text-caption">
|
||||||
|
<div v-if="data.dropDownApp?.name">
|
||||||
|
{{ `${data.sourceApp?.name} -> ${data.dropDownApp?.name}` }}
|
||||||
|
</div>
|
||||||
|
<div v-else>{{ placeholder }}</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</template>
|
||||||
|
</q-field>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<ShowDialog v-model:visible="dgIsShow" name="ドロップダウン階層化設定" @close="closeDg" min-width="50vw" min-height="20vh" disableBtn>
|
||||||
|
<template v-slot:toolbar>
|
||||||
|
<q-btn flat round dense icon="more_vert" >
|
||||||
|
<q-menu auto-close anchor="bottom start">
|
||||||
|
<q-list>
|
||||||
|
<q-item clickable @click="copySetting()">
|
||||||
|
<q-item-section avatar><q-icon name="content_copy" ></q-icon></q-item-section>
|
||||||
|
<q-item-section >コピー</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<q-item clickable @click="pasteSetting()">
|
||||||
|
<q-item-section avatar><q-icon name="content_paste" ></q-icon></q-item-section>
|
||||||
|
<q-item-section >貼り付け</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-menu>
|
||||||
|
</q-btn>
|
||||||
|
</template>
|
||||||
|
<div class="q-mb-md q-ml-md q-mr-md">
|
||||||
|
<CascadingDropDownBox v-model:model-value="data" :finishDialogHandler="finishDialogHandler" />
|
||||||
|
</div>
|
||||||
|
</ShowDialog>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { defineComponent, ref, watchEffect } from 'vue';
|
||||||
|
import ShowDialog from '../ShowDialog.vue';
|
||||||
|
import CascadingDropDownBox from '../CascadingDropDownBox.vue';
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
inheritAttrs: false,
|
||||||
|
name: 'CascadingDropDown',
|
||||||
|
components: {
|
||||||
|
ShowDialog,
|
||||||
|
CascadingDropDownBox
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
displayName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: Object,
|
||||||
|
default: () => { ({}) }
|
||||||
|
},
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const dgIsShow = ref(false);
|
||||||
|
// const data = ref(props.modelValue);
|
||||||
|
const data = ref({
|
||||||
|
sourceApp: props.modelValue.sourceApp ?? { appFilter: '', showSelectApp: false },
|
||||||
|
dropDownApp: props.modelValue.dropDownApp,
|
||||||
|
fieldList: props.modelValue.fieldList ?? [],
|
||||||
|
});
|
||||||
|
const closeDg = (state: string) => {
|
||||||
|
dgIsShow.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const finishDialogHandler = (boxData) => {
|
||||||
|
data.value = boxData
|
||||||
|
dgIsShow.value = false
|
||||||
|
emit('update:modelValue', data.value);
|
||||||
|
}
|
||||||
|
|
||||||
|
//設定をコピーする
|
||||||
|
const copySetting=()=>{
|
||||||
|
if (navigator.clipboard) {
|
||||||
|
const jsonData= JSON.stringify(data.value);
|
||||||
|
navigator.clipboard.writeText(jsonData).then(() => {
|
||||||
|
console.log('Text successfully copied to clipboard');
|
||||||
|
},
|
||||||
|
(err) => {
|
||||||
|
console.error('Error in copying text: ', err);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.log('Clipboard API not available');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
//設定を貼り付ける
|
||||||
|
const pasteSetting=async ()=>{
|
||||||
|
try {
|
||||||
|
const text = await navigator.clipboard.readText();
|
||||||
|
console.log('Text from clipboard:', text);
|
||||||
|
const jsonData=JSON.parse(text);
|
||||||
|
if('sourceApp' in jsonData && 'dropDownApp' in jsonData && 'fieldList' in jsonData){
|
||||||
|
const {sourceApp,dropDownApp, fieldList}=jsonData;
|
||||||
|
data.value.sourceApp=sourceApp;
|
||||||
|
data.value.dropDownApp=dropDownApp;
|
||||||
|
data.value.fieldList=fieldList;
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Failed to read text from clipboard: ', err);
|
||||||
|
throw err;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// watchEffect(() => {
|
||||||
|
// emit('update:modelValue', data.value);
|
||||||
|
// });
|
||||||
|
|
||||||
|
return {
|
||||||
|
dgIsShow,
|
||||||
|
closeDg,
|
||||||
|
data,
|
||||||
|
finishDialogHandler,
|
||||||
|
copySetting,
|
||||||
|
pasteSetting
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="" v-bind="$attrs">
|
<div class="" v-bind="$attrs">
|
||||||
<q-field v-model="color" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label :bottom-slots="!isSelected" >
|
<q-field v-model="color" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label :bottom-slots="!isSelected" :rules="rulesExp">
|
||||||
<template v-slot:control>
|
<template v-slot:control>
|
||||||
<q-chip text-color="black" color="white" v-if="isSelected">
|
<q-chip text-color="black" color="white" v-if="isSelected">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
@@ -57,17 +57,34 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
//例:[val=>!!val ||'入力してください']
|
||||||
|
rules: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
required:{
|
||||||
|
type:Boolean,
|
||||||
|
default:false
|
||||||
|
},
|
||||||
|
requiredMessage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const color = ref(props.modelValue??"");
|
const color = ref(props.modelValue??"");
|
||||||
const isSelected = computed(()=>props.modelValue && props.modelValue!=="");
|
const isSelected = computed(()=>props.modelValue && props.modelValue!=="");
|
||||||
|
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||||
|
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||||
|
const requiredExp = props.required?[((val:any)=>!!val || errmsg ),"anyColor"]:[];
|
||||||
|
const rulesExp=[...requiredExp,...customExp];
|
||||||
watchEffect(()=>{
|
watchEffect(()=>{
|
||||||
emit('update:modelValue', color.value);
|
emit('update:modelValue', color.value);
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
color,
|
color,
|
||||||
isSelected
|
isSelected,
|
||||||
|
rulesExp
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<div v-bind="$attrs">
|
||||||
<q-field v-model="tree" :label="displayName" labelColor="primary" stack-label >
|
<q-field v-model="tree" :label="displayName" labelColor="primary" stack-label>
|
||||||
<template v-slot:control >
|
<template v-slot:control>
|
||||||
<q-card flat class="full-width">
|
<q-card flat class="full-width">
|
||||||
<q-card-actions vertical>
|
<q-card-actions vertical>
|
||||||
<q-btn color="grey-3" text-color="black" @click="showDg()">クリックで設定:{{ isSetted?'設定済み':'未設定' }}</q-btn>
|
<q-btn color="grey-3" text-color="black" :disable="btnDisable" @click="showDg()">クリックで設定:{{ isSetted ?
|
||||||
|
'設定済み' : '未設定' }}</q-btn>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
<q-card-section class="text-caption" >
|
<q-card-section class="text-caption">
|
||||||
<div v-if="!isSetted">{{ placeholder }}</div>
|
<div v-if="!isSetted">{{ placeholder }}</div>
|
||||||
<div v-else>{{ conditionString }}</div>
|
<div v-else>{{ conditionString }}</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
@@ -17,82 +18,157 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent, ref ,watchEffect,computed,reactive} from 'vue';
|
import { ConditionNode, ConditionTree, Operator, OperatorListItem } from 'app/src/types/Conditions';
|
||||||
import { ConditionTree,GroupNode,ConditionNode,LogicalOperator,Operator } from 'app/src/types/Conditions';
|
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
|
||||||
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue'
|
import ConditionEditor from '../ConditionEditor/ConditionEditor.vue';
|
||||||
export default defineComponent({
|
import { IActionProperty } from 'src/types/ActionTypes';
|
||||||
name: 'FieldInput',
|
|
||||||
inheritAttrs:false,
|
|
||||||
components: {
|
|
||||||
ConditionEditor
|
|
||||||
},
|
|
||||||
props: {
|
|
||||||
displayName:{
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
name:{
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
placeholder: {
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
hint:{
|
|
||||||
type: String,
|
|
||||||
default: '',
|
|
||||||
},
|
|
||||||
modelValue: {
|
|
||||||
type: String,
|
|
||||||
default: null
|
|
||||||
},
|
|
||||||
},
|
|
||||||
|
|
||||||
setup(props, { emit }) {
|
export default defineComponent({
|
||||||
const appDg = ref();
|
name: 'FieldInput',
|
||||||
const show = ref(false);
|
inheritAttrs: false,
|
||||||
const tree = reactive(new ConditionTree());
|
components: {
|
||||||
if(props.modelValue && props.modelValue!==''){
|
ConditionEditor
|
||||||
tree.fromJson(props.modelValue);
|
},
|
||||||
}else{
|
props: {
|
||||||
const newNode = new ConditionNode({},Operator.Equal,'',tree.root);
|
context: {
|
||||||
tree.addNode(tree.root,newNode);
|
type: Array<IActionProperty>,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
displayName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
hint: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: String,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
sourceType: {
|
||||||
|
type: String,
|
||||||
|
default: 'field'
|
||||||
|
},
|
||||||
|
connectProps:{
|
||||||
|
type:Object,
|
||||||
|
default:()=>({})
|
||||||
|
},
|
||||||
|
onlySourceSelect: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
operatorList: {
|
||||||
|
type: Array,
|
||||||
|
},
|
||||||
|
inputConfig: {
|
||||||
|
type: Object,
|
||||||
|
default: () => ({
|
||||||
|
left: {
|
||||||
|
canInput: false,
|
||||||
|
buttonsConfig: [
|
||||||
|
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
||||||
|
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
right: {
|
||||||
|
canInput: true,
|
||||||
|
buttonsConfig: [
|
||||||
|
{ label: '変数', color: 'green', type: 'VariableAdd' },
|
||||||
|
]
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setup(props, { emit }) {
|
||||||
|
let source = reactive(props.connectProps["source"]);
|
||||||
|
if(!source){
|
||||||
|
source = props.context.find(element => element.props.name === 'sources');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (source) {
|
||||||
|
if (props.sourceType === 'field') {
|
||||||
|
provide('sourceFields', computed(() => source.props?.modelValue?.fields ?? []));
|
||||||
|
} else if (props.sourceType === 'app') {
|
||||||
|
provide('sourceApp', computed(() => source.props?.modelValue?.app?.id));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
provide('leftDynamicItemConfig', props.inputConfig.left);
|
||||||
|
provide('rightDynamicItemConfig', props.inputConfig.right);
|
||||||
|
provide('Operator', props.operatorList);
|
||||||
|
|
||||||
|
const btnDisable = computed(() => {
|
||||||
|
const onlySourceSelect = props.onlySourceSelect;
|
||||||
|
|
||||||
|
if (!onlySourceSelect) {
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isSetted=ref(props.modelValue && props.modelValue!=='');
|
if (props.sourceType === 'field') {
|
||||||
|
return source?.props?.modelValue?.fields?.length ?? 0 > 0;
|
||||||
|
} else if (props.sourceType === 'app') {
|
||||||
|
return source?.props?.modelValue?.app?.id ? false : true
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
|
||||||
const conditionString = computed(()=>{
|
const appDg = ref();
|
||||||
return tree.buildConditionString(tree.root);
|
const show = ref(false);
|
||||||
});
|
const tree = reactive(new ConditionTree());
|
||||||
|
if (props.modelValue && props.modelValue !== '') {
|
||||||
|
tree.fromJson(props.modelValue);
|
||||||
|
} else {
|
||||||
|
const newNode = new ConditionNode({}, (props.operatorList && props.operatorList.length > 0) ? props.operatorList[0] as OperatorListItem : Operator.Equal, '', tree.root);
|
||||||
|
tree.addNode(tree.root, newNode);
|
||||||
|
}
|
||||||
|
|
||||||
const showDg = () => {
|
const isSetted = ref(props.modelValue && props.modelValue !== '');
|
||||||
show.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const onClosed = (val:string) => {
|
const conditionString = computed(() => {
|
||||||
if (val == 'OK') {
|
const condiStr= tree.buildConditionString(tree.root);
|
||||||
const conditionJson = tree.toJson();
|
return condiStr==='()'?'(条件なし)':condiStr;
|
||||||
isSetted.value=true;
|
});
|
||||||
emit('update:modelValue', conditionJson);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
watchEffect(() => {
|
const showDg = () => {
|
||||||
|
show.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onClosed = (val: string) => {
|
||||||
|
if (val == 'OK') {
|
||||||
|
isSetted.value = true;
|
||||||
|
tree.setQuery(tree.buildConditionQueryString(tree.root));
|
||||||
const conditionJson = tree.toJson();
|
const conditionJson = tree.toJson();
|
||||||
emit('update:modelValue', conditionJson);
|
emit('update:modelValue', conditionJson);
|
||||||
});
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return {
|
watchEffect(() => {
|
||||||
appDg,
|
tree.setQuery(tree.buildConditionQueryString(tree.root));
|
||||||
isSetted,
|
const conditionJson = tree.toJson();
|
||||||
show,
|
emit('update:modelValue', conditionJson);
|
||||||
showDg,
|
});
|
||||||
onClosed,
|
|
||||||
tree,
|
return {
|
||||||
conditionString
|
appDg,
|
||||||
};
|
isSetted,
|
||||||
}
|
show,
|
||||||
});
|
showDg,
|
||||||
</script>
|
onClosed,
|
||||||
|
tree,
|
||||||
|
conditionString,
|
||||||
|
btnDisable
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|||||||
322
frontend/src/components/right/DataMapping.vue
Normal file
322
frontend/src/components/right/DataMapping.vue
Normal file
@@ -0,0 +1,322 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-my-md" v-bind="$attrs">
|
||||||
|
<q-field :label="displayName" labelColor="primary" stack-label
|
||||||
|
v-model="mappingProps"
|
||||||
|
:rules="rulesExp"
|
||||||
|
ref="fieldRef"
|
||||||
|
>
|
||||||
|
<template v-slot:control>
|
||||||
|
<q-card flat class="full-width">
|
||||||
|
<q-card-actions vertical>
|
||||||
|
<q-btn color="grey-3" text-color="black" :disable="btnDisable"
|
||||||
|
@click="() => { dgIsShow = true }">クリックで設定</q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
<q-card-section class="text-caption">
|
||||||
|
<div v-if="mappingObjectsInputDisplay && mappingObjectsInputDisplay.length > 0">
|
||||||
|
<div v-for="(item) in mappingObjectsInputDisplay" :key="item">{{ item }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>{{ placeholder }}</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</template>
|
||||||
|
</q-field>
|
||||||
|
<show-dialog v-model:visible="dgIsShow" name="データマッピング" @close="closeDg" min-width="55vw" min-height="60vh">
|
||||||
|
<div class="">
|
||||||
|
<div class="row q-col-gutter-x-xs flex-center">
|
||||||
|
<div class="col-5">
|
||||||
|
<div class="q-mx-xs">ソース</div>
|
||||||
|
</div>
|
||||||
|
<!-- <div class="col-1">
|
||||||
|
</div> -->
|
||||||
|
<div class="col-5">
|
||||||
|
<div class="row justify-between q-mr-md">
|
||||||
|
<div class="">{{ sourceApp?.name }}</div>
|
||||||
|
<q-btn outline color="primary" size="xs" label="最新のフィールドを取得する"
|
||||||
|
@click="() => updateFields(sourceAppId!)" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-1 q-pl-sm">
|
||||||
|
キー
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<q-virtual-scroll style="max-height: 60vh;" :items="mappingProps.data" separator v-slot="{ item, index }">
|
||||||
|
<!-- <div class="q-my-sm" v-for="(item, index) in mappingProps" :key="item.id"> -->
|
||||||
|
<div class="row q-pa-sm q-col-gutter-x-md flex-center">
|
||||||
|
<div class="col-5">
|
||||||
|
<ConditionObject :config="config" v-model="item.from" :disabled="item.disabled"
|
||||||
|
:label="item.disabled ? '「Lookup」によってロックされる' : undefined" />
|
||||||
|
</div>
|
||||||
|
<!-- <div class="col-1">
|
||||||
|
</div> -->
|
||||||
|
<div class="col-5">
|
||||||
|
<q-field v-model="item.vName" type="text" outlined dense :disable="item.disabled" >
|
||||||
|
<!-- <template v-slot:append>
|
||||||
|
<q-icon name="search" class="cursor-pointer"
|
||||||
|
@click="() => { mappingProps[index].to.isDialogVisible = true }" />
|
||||||
|
</template> -->
|
||||||
|
<template v-slot:control>
|
||||||
|
<div class="self-center full-width no-outline" tabindex="0"
|
||||||
|
v-if="item.to.app?.name && item.to.fields?.length > 0 && item.to.fields[0].label">
|
||||||
|
{{ `${item.to.fields[0].label}` }}
|
||||||
|
<span class="text-red" v-if="item.to.fields[0].required">*</span>
|
||||||
|
<q-tooltip class="bg-yellow-2 text-black shadow-4" >
|
||||||
|
<div>アプリ : {{ item.to.app.name }}</div>
|
||||||
|
<div>フィールドのコード : {{ item.to.fields[0].code }}</div>
|
||||||
|
<div>フィールドのタイプ : {{ item.to.fields[0].type }}</div>
|
||||||
|
<div v-if="item.to.fields[0].required">必須項目</div>
|
||||||
|
<!-- <div>フィールド : {{ item.to.fields[0] }}</div>
|
||||||
|
<div>フィールド : {{ item.isKey }}</div> -->
|
||||||
|
</q-tooltip>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</q-field>
|
||||||
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<q-checkbox size="sm" v-model="item.isKey" :disable="item.disabled" />
|
||||||
|
<!-- <q-btn flat round dense icon="delete" size="sm" @click="() => deleteMappingObject(index)" /> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<show-dialog v-model:visible="mappingProps.data[index].to.isDialogVisible" name="フィールド一覧"
|
||||||
|
@close="closeToDg" ref="fieldDlg">
|
||||||
|
<FieldSelect v-if="onlySourceSelect" ref="fieldDlg" name="フィールド" :appId="sourceAppId" not_page
|
||||||
|
:selectedFields="mappingProps.data[index].to.fields"
|
||||||
|
:updateSelects="(fields) => { mappingProps.data[index].to.fields = fields; mappingProps.data[index].to.app = sourceApp }">
|
||||||
|
</FieldSelect>
|
||||||
|
<AppFieldSelectBox v-else v-model:selectedField="mappingProps.data[index].to" />
|
||||||
|
</show-dialog>
|
||||||
|
<!-- </div> -->
|
||||||
|
</q-virtual-scroll>
|
||||||
|
|
||||||
|
<div class="q-mt-lg q-ml-md row ">
|
||||||
|
<q-checkbox size="sm" v-model="mappingProps.createWithNull" label="キーが存在しない場合は新規に作成され、存在する場合はデータが更新されます。" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</show-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { computed, defineComponent, watch, isRef, reactive, ref, watchEffect } from 'vue';
|
||||||
|
import ConditionObject from '../ConditionEditor/ConditionObject.vue';
|
||||||
|
import ShowDialog from '../ShowDialog.vue';
|
||||||
|
import AppFieldSelectBox from '../AppFieldSelectBox.vue';
|
||||||
|
import FieldSelect from '../FieldSelect.vue';
|
||||||
|
import { IApp, IField } from './AppFieldSelect.vue';
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
|
||||||
|
type ContextProps = {
|
||||||
|
props?: {
|
||||||
|
name: string;
|
||||||
|
modelValue?: {
|
||||||
|
app: {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
interface IMappingSetting {
|
||||||
|
data: IMappingValueType[];
|
||||||
|
createWithNull: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface IMappingValueType {
|
||||||
|
id: string;
|
||||||
|
from: { sharedText?: string };
|
||||||
|
to: {
|
||||||
|
app?: IApp,
|
||||||
|
fields: IField[],
|
||||||
|
isDialogVisible: boolean;
|
||||||
|
};
|
||||||
|
isKey: boolean;
|
||||||
|
disabled: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const blackListLabelName = ['レコード番号', '作業者', '更新者', '更新日時', '作成日時', '作成者']
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'DataMapping',
|
||||||
|
inheritAttrs: false,
|
||||||
|
components: {
|
||||||
|
ShowDialog,
|
||||||
|
ConditionObject,
|
||||||
|
AppFieldSelectBox,
|
||||||
|
FieldSelect
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
context: {
|
||||||
|
type: Array<ContextProps>,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
displayName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: Object as () => IMappingSetting,
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
onlySourceSelect: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
fieldTypes:{
|
||||||
|
type:Array,
|
||||||
|
default:()=>[]
|
||||||
|
},
|
||||||
|
//例:[val=>!!val ||'入力してください']
|
||||||
|
rules: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
requiredMessage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
},
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const fieldRef=ref();
|
||||||
|
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||||
|
|
||||||
|
const sourceApp = computed(() => source?.props?.modelValue?.app);
|
||||||
|
|
||||||
|
const sourceAppId = computed(() => sourceApp.value?.id);
|
||||||
|
|
||||||
|
//ルール設定
|
||||||
|
const checkMapping = (val:IMappingSetting)=>{
|
||||||
|
if(!val || !val.data){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
console.log(val);
|
||||||
|
const mappingDatas = val.data.filter(item=>item.from?.sharedText && item.to.fields?.length > 0);
|
||||||
|
return mappingDatas.length>0;
|
||||||
|
}
|
||||||
|
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||||
|
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
|
||||||
|
const requiredExp = props.required ? [((val: any) => checkMapping(val) || errmsg)] : [];
|
||||||
|
const rulesExp = [...requiredExp, ...customExp];
|
||||||
|
|
||||||
|
// const mappingProps = ref(props.modelValue?.data ?? []);
|
||||||
|
|
||||||
|
// const createWithNull = ref(props.modelValue?.createWithNull ?? false);
|
||||||
|
|
||||||
|
const mappingProps = reactive<IMappingSetting>({
|
||||||
|
data:props.modelValue?.data ?? [],
|
||||||
|
createWithNull:props.modelValue?.createWithNull ?? false
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeDg = () => {
|
||||||
|
fieldRef.value.validate();
|
||||||
|
emit('update:modelValue',mappingProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeToDg = () => {
|
||||||
|
emit('update:modelValue',mappingProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 外部ソースコンポーネントの appid をリッスンし、変更されたときに現在のコンポーネントを更新します
|
||||||
|
watch(() => sourceAppId.value, async (newId,) => {
|
||||||
|
if (!newId) return;
|
||||||
|
updateFields(newId)
|
||||||
|
})
|
||||||
|
|
||||||
|
const updateFields = async (sourceAppId: string) => {
|
||||||
|
const ktAppFields = await api.get('api/v1/appfields', {
|
||||||
|
params: {
|
||||||
|
app: sourceAppId
|
||||||
|
}
|
||||||
|
}).then(res => {
|
||||||
|
return Object.values(res.data.properties)
|
||||||
|
// kintoneのデフォルトの非表示フィールドフィルタリング
|
||||||
|
.filter(f => !blackListLabelName.find(label => f.label === label))
|
||||||
|
.map(f => ({ name: f.label, objectType: 'field', ...f }))
|
||||||
|
.map(f => {
|
||||||
|
// 更新前の値を求める
|
||||||
|
const beforeData = mappingProps.data.find(m => m.to.fields[0].code === f.code)
|
||||||
|
return {
|
||||||
|
id: uuidv4(),
|
||||||
|
from: beforeData?.from ?? {}, // 以前のデータを入力します
|
||||||
|
to: {
|
||||||
|
app: sourceApp.value,
|
||||||
|
fields: [f],
|
||||||
|
isDialogVisible: false
|
||||||
|
},
|
||||||
|
isKey: beforeData?.isKey ?? false, // 以前のデータを入力します
|
||||||
|
disabled: false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 「ルックアップ」によってロックされているフィールドを検索する
|
||||||
|
const lookupFixedField = ktAppFields
|
||||||
|
.filter(field => field.to.fields[0].lookup !== undefined)
|
||||||
|
.flatMap(field => field.to.fields[0].lookup.fieldMappings.map((m) => m.field))
|
||||||
|
|
||||||
|
// 「ルックアップ」でロックされたビューコンポーネントを非対話型に設定します
|
||||||
|
if (lookupFixedField.length > 0) {
|
||||||
|
ktAppFields.filter(f => lookupFixedField.includes(f.to.fields[0].code)).forEach(f => f.disabled = true)
|
||||||
|
}
|
||||||
|
|
||||||
|
mappingProps.data = ktAppFields
|
||||||
|
}
|
||||||
|
|
||||||
|
const mappingObjectsInputDisplay = computed(() =>
|
||||||
|
(mappingProps.data && Array.isArray(mappingProps.data)) ?
|
||||||
|
mappingProps.data
|
||||||
|
.filter(item => item.from?.sharedText && item.to.fields?.length > 0)
|
||||||
|
.map(item => {
|
||||||
|
return `field(${item.to.app?.id},${item.to.fields[0].label}) = ${item.from.sharedText} `;
|
||||||
|
})
|
||||||
|
: []
|
||||||
|
);
|
||||||
|
|
||||||
|
const btnDisable = computed(() => props.onlySourceSelect ? !(source?.props?.modelValue?.app?.id) : false);
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
emit('update:modelValue', mappingProps);
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
uuidv4,
|
||||||
|
dgIsShow: ref(false),
|
||||||
|
fieldRef,
|
||||||
|
closeDg,
|
||||||
|
toDgIsShow: ref(false),
|
||||||
|
closeToDg,
|
||||||
|
mappingProps,
|
||||||
|
updateFields,
|
||||||
|
// addMappingObject: () => mappingProps.push(defaultMappingProp()),
|
||||||
|
// deleteMappingObject,
|
||||||
|
mappingObjectsInputDisplay,
|
||||||
|
sourceApp,
|
||||||
|
sourceAppId,
|
||||||
|
btnDisable,
|
||||||
|
rulesExp,
|
||||||
|
checkMapping,
|
||||||
|
config: {
|
||||||
|
canInput: false,
|
||||||
|
buttonsConfig: [
|
||||||
|
{ label: 'フィールド', color: 'primary', type: 'FieldAdd' },
|
||||||
|
{ label: '変数', color: 'green', type: 'VariableAdd', editable: false },
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
275
frontend/src/components/right/DataProcessing.vue
Normal file
275
frontend/src/components/right/DataProcessing.vue
Normal file
@@ -0,0 +1,275 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<q-field :label="displayName" labelColor="primary" stack-label
|
||||||
|
v-model="processingProps"
|
||||||
|
:rules="rulesExp"
|
||||||
|
lazy-rules="ondemand"
|
||||||
|
ref="fieldRef"
|
||||||
|
>
|
||||||
|
<template v-slot:control>
|
||||||
|
<q-card flat class="full-width">
|
||||||
|
<q-card-actions vertical>
|
||||||
|
<q-btn color="grey-3" text-color="black" @click="() => { dgIsShow = true }">クリックで設定</q-btn>
|
||||||
|
</q-card-actions>
|
||||||
|
<q-card-section class="text-caption">
|
||||||
|
<div v-if="processingObjectsInputDisplay && processingObjectsInputDisplay.length>0">
|
||||||
|
<div v-for="(item) in processingObjectsInputDisplay" :key="item">{{ item }}</div>
|
||||||
|
</div>
|
||||||
|
<div v-else>{{ placeholder }}</div>
|
||||||
|
</q-card-section>
|
||||||
|
</q-card>
|
||||||
|
</template>
|
||||||
|
</q-field>
|
||||||
|
<show-dialog v-model:visible="dgIsShow" name="集計処理" @close="closeDg" min-width="50vw" min-height="60vh">
|
||||||
|
<div class="q-mx-md q-mb-md">
|
||||||
|
<q-input v-model="processingProps.name" type="text" label-color="primary" label="集計結果の変数名"
|
||||||
|
placeholder="集計結果を格納する変数名を入力してください" stack-label />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="q-mx-md">
|
||||||
|
<div class="row q-col-gutter-x-xs flex-center">
|
||||||
|
<div class="col-5">
|
||||||
|
<div class="q-mx-xs">データソース</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-2">
|
||||||
|
<div class="q-mx-xs">集計計算</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<div class="q-mx-xs">集計結果変数名</div>
|
||||||
|
</div>
|
||||||
|
<div class="col-1"><q-btn flat round dense icon="add" size="sm" @click="addProcessingObject" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="q-my-sm" v-for="(item, index) in processingObjects" :key="item.id">
|
||||||
|
<div class="row q-col-gutter-x-xs flex-center">
|
||||||
|
<div class="col-5">
|
||||||
|
<ConditionObject v-model="item.field" />
|
||||||
|
</div>
|
||||||
|
<div class="col-2 q-pa-sm">
|
||||||
|
<q-select v-model="item.logicalOperator" :options="logicalOperators" outlined dense></q-select>
|
||||||
|
</div>
|
||||||
|
<div class="col-4">
|
||||||
|
<q-input v-model="item.vName" type="text" outlined dense />
|
||||||
|
</div>
|
||||||
|
<div class="col-1">
|
||||||
|
<q-btn flat round dense icon="delete" size="sm" @click="() => deleteProcessingObject(index)" />
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</show-dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts">
|
||||||
|
import { v4 as uuidv4 } from 'uuid';
|
||||||
|
import { computed, defineComponent, provide, reactive, ref, watchEffect } from 'vue';
|
||||||
|
import ConditionObject from '../ConditionEditor/ConditionObject.vue';
|
||||||
|
import ShowDialog from '../ShowDialog.vue';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
props?: {
|
||||||
|
name: string;
|
||||||
|
modelValue?: {
|
||||||
|
fields: {
|
||||||
|
type: string;
|
||||||
|
label: string;
|
||||||
|
code: string;
|
||||||
|
}[]
|
||||||
|
} | string
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
type ProcessingObjectType = {
|
||||||
|
field?: {
|
||||||
|
sharedText: string;
|
||||||
|
objectType: 'field';
|
||||||
|
};
|
||||||
|
logicalOperator?: string;
|
||||||
|
vName?: string;
|
||||||
|
id: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
type ValueType = {
|
||||||
|
name: string;
|
||||||
|
actionName: string,
|
||||||
|
displayName: string,
|
||||||
|
vars: ProcessingObjectType[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export default defineComponent({
|
||||||
|
name: 'DataProcessing',
|
||||||
|
inheritAttrs: false,
|
||||||
|
components: {
|
||||||
|
ShowDialog,
|
||||||
|
ConditionObject,
|
||||||
|
},
|
||||||
|
props: {
|
||||||
|
context: {
|
||||||
|
type: Array<Props>,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
displayName: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
name: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
modelValue: {
|
||||||
|
type: Object as () => ValueType,
|
||||||
|
},
|
||||||
|
placeholder: {
|
||||||
|
type: String,
|
||||||
|
default: '',
|
||||||
|
},
|
||||||
|
//例:[val=>!!val ||'入力してください']
|
||||||
|
rules: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
setup(props, { emit }) {
|
||||||
|
const fieldRef=ref();
|
||||||
|
const source = props.context.find(element => element?.props?.name === 'sources')
|
||||||
|
|
||||||
|
if (source) {
|
||||||
|
provide('sourceFields', computed(() => {
|
||||||
|
const modelValue = source.props?.modelValue;
|
||||||
|
if (modelValue && typeof modelValue !== 'string') {
|
||||||
|
return modelValue.fields;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}));
|
||||||
|
provide('sourceApp', computed(() => source.props?.modelValue?.app?.id));
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionName = props.context.find(element => element?.props?.name === 'displayName')
|
||||||
|
|
||||||
|
const processingProps: ValueType = props.modelValue && props.modelValue.vars
|
||||||
|
? reactive(props.modelValue)
|
||||||
|
: reactive({
|
||||||
|
name: '',
|
||||||
|
actionName: actionName?.props?.modelValue as string,
|
||||||
|
displayName: '結果(戻り値)',
|
||||||
|
vars: [
|
||||||
|
{
|
||||||
|
id: uuidv4(),
|
||||||
|
field:{
|
||||||
|
objectType:'field',
|
||||||
|
sharedText:''
|
||||||
|
}
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
|
||||||
|
const closeDg = () => {
|
||||||
|
fieldRef.value.validate();
|
||||||
|
emit('update:modelValue', processingProps);
|
||||||
|
}
|
||||||
|
|
||||||
|
const processingObjects = processingProps.vars;
|
||||||
|
|
||||||
|
const deleteProcessingObject = (index: number) => {
|
||||||
|
if(processingObjects.length >0){
|
||||||
|
processingObjects.splice(index, 1);
|
||||||
|
}
|
||||||
|
if(processingObjects.length===0){
|
||||||
|
addProcessingObject();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const processingObjectsInputDisplay = computed(() =>
|
||||||
|
processingObjects ?
|
||||||
|
processingObjects
|
||||||
|
.filter(item => item.field && item.logicalOperator && item.vName)
|
||||||
|
.map(item => {
|
||||||
|
return`var(${processingProps.name}.${item.vName}) = ${item.field?.sharedText}`
|
||||||
|
})
|
||||||
|
: []
|
||||||
|
);
|
||||||
|
|
||||||
|
const addProcessingObject=()=>{
|
||||||
|
processingObjects.push({
|
||||||
|
id: uuidv4(),
|
||||||
|
field:{
|
||||||
|
objectType:'field',
|
||||||
|
sharedText:''
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//集計処理方法
|
||||||
|
const logicalOperators = ref([
|
||||||
|
{
|
||||||
|
"operator": "",
|
||||||
|
"label": "なし"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"operator": "SUM",
|
||||||
|
"label": "合計"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"operator": "AVG",
|
||||||
|
"label": "平均"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"operator": "MAX",
|
||||||
|
"label": "最大値"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"operator": "MIN",
|
||||||
|
"label": "最小値"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"operator": "COUNT",
|
||||||
|
"label": "カウント"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"operator": "FIRST",
|
||||||
|
"label": "最初の値"
|
||||||
|
}
|
||||||
|
]);
|
||||||
|
const checkInput=(val:ValueType)=>{
|
||||||
|
if(!val){
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if(!val.name){
|
||||||
|
return "集計結果の変数名を入力してください";
|
||||||
|
}
|
||||||
|
if(!val.vars || val.vars.length==0){
|
||||||
|
return "集計処理を設定してください";
|
||||||
|
}
|
||||||
|
if(val.vars.some((x)=>!x.vName)){
|
||||||
|
return "集計結果変数名を入力してください";
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||||
|
const requiredExp = props.required ? [(val: any) => checkInput(val)] : [];
|
||||||
|
const rulesExp = [...requiredExp, ...customExp];
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
emit('update:modelValue', processingProps);
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
uuidv4,
|
||||||
|
dgIsShow: ref(false),
|
||||||
|
closeDg,
|
||||||
|
processingObjects,
|
||||||
|
processingProps,
|
||||||
|
addProcessingObject,
|
||||||
|
deleteProcessingObject,
|
||||||
|
logicalOperators,
|
||||||
|
processingObjectsInputDisplay,
|
||||||
|
rulesExp,
|
||||||
|
fieldRef
|
||||||
|
};
|
||||||
|
},
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
<style lang="scss"></style>
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<div v-bind="$attrs">
|
||||||
<q-input v-model="selectedDate" :label="displayName" :placeholder="placeholder" label-color="primary" mask="date" :rules="['date']" stack-label>
|
<q-input v-model="selectedDate" :label="displayName" :placeholder="placeholder" label-color="primary" mask="date" :rules="rulesExp" stack-label>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-icon name="event" class="cursor-pointer">
|
<q-icon name="event" class="cursor-pointer">
|
||||||
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
<q-popup-proxy cover transition-show="scale" transition-hide="scale">
|
||||||
@@ -43,16 +43,32 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
//例:[val=>!!val ||'入力してください']
|
||||||
|
rules: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
required:{
|
||||||
|
type:Boolean,
|
||||||
|
default:false
|
||||||
|
},
|
||||||
|
requiredMessage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const selectedDate = ref(props.modelValue);
|
const selectedDate = ref(props.modelValue);
|
||||||
|
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||||
|
const requiredExp = props.required?[((val:any)=>!!val||`${props.displayName}が必須です。`),'date']:['date'];
|
||||||
|
const rulesExp=[...requiredExp,...customExp];
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
emit('update:modelValue', selectedDate.value);
|
emit('update:modelValue', selectedDate.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
selectedDate
|
selectedDate,
|
||||||
|
rulesExp
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<div v-bind="$attrs">
|
||||||
<q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label>
|
<q-input :label="displayName" v-model="inputValue" label-color="primary"
|
||||||
|
:placeholder="placeholder"
|
||||||
|
:rules="rulesExp"
|
||||||
|
stack-label>
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-btn round dense flat icon="add" @click="addButtonEvent()" />
|
<q-btn round dense flat icon="add" @click="addButtonEvent()" />
|
||||||
</template>
|
</template>
|
||||||
@@ -40,12 +43,29 @@ export default defineComponent({
|
|||||||
connectProps:{
|
connectProps:{
|
||||||
type:Object,
|
type:Object,
|
||||||
default:undefined
|
default:undefined
|
||||||
|
},
|
||||||
|
//例:[val=>!!val ||'入力してください']
|
||||||
|
rules: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
required:{
|
||||||
|
type:Boolean,
|
||||||
|
default:false
|
||||||
|
},
|
||||||
|
requiredMessage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props , { emit }) {
|
setup(props , { emit }) {
|
||||||
const inputValue = ref(props.modelValue);
|
const inputValue = ref(props.modelValue);
|
||||||
const store = useFlowEditorStore();
|
const store = useFlowEditorStore();
|
||||||
|
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||||
|
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を入力してください。`;
|
||||||
|
const requiredExp = props.required ? [((val: any) => !!val || errmsg)] : [];
|
||||||
|
const rulesExp = [...requiredExp, ...customExp];
|
||||||
const addButtonEvent=()=>{
|
const addButtonEvent=()=>{
|
||||||
const eventId =store.currentFlow?.getRoot()?.name;
|
const eventId =store.currentFlow?.getRoot()?.name;
|
||||||
if(eventId===undefined){return;}
|
if(eventId===undefined){return;}
|
||||||
@@ -61,22 +81,22 @@ export default defineComponent({
|
|||||||
if(store.eventTree.findEventById(addEventId)){
|
if(store.eventTree.findEventById(addEventId)){
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
customEvents.events.push(
|
customEvents.events.push(new kintoneEvent(
|
||||||
new kintoneEvent(
|
displayName,
|
||||||
displayName,
|
addEventId,
|
||||||
addEventId,
|
customButtonId,
|
||||||
customButtonId)
|
'DELETABLE'
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
emit('update:modelValue', inputValue.value);
|
emit('update:modelValue', inputValue.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputValue,
|
inputValue,
|
||||||
addButtonEvent
|
addButtonEvent,
|
||||||
|
rulesExp
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<div v-bind="$attrs">
|
||||||
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
|
<q-field v-model="selectedField" :label="displayName" labelColor="primary" :clearable="isSelected" stack-label
|
||||||
:bottom-slots="!isSelected">
|
:bottom-slots="!isSelected"
|
||||||
|
:rules="rulesExp"
|
||||||
|
>
|
||||||
<template v-slot:control>
|
<template v-slot:control>
|
||||||
<q-chip color="primary" text-color="white" v-if="isSelected">
|
<q-chip color="primary" text-color="white" v-if="isSelected">
|
||||||
{{ selectedField.name }}
|
{{ selectedField.name }}
|
||||||
</q-chip>
|
</q-chip>
|
||||||
</template>
|
</template>
|
||||||
<!-- <template v-slot:hint v-if="isSelected">
|
|
||||||
<div> 項目コード:<q-chip size="sm" outline color="secondary" text-color="white">{{selectedField.code}}</q-chip></div>
|
|
||||||
</template> -->
|
|
||||||
<template v-slot:hint v-if="!isSelected">
|
<template v-slot:hint v-if="!isSelected">
|
||||||
{{ placeholder }}
|
{{ placeholder }}
|
||||||
</template>
|
</template>
|
||||||
@@ -18,8 +17,15 @@
|
|||||||
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
|
<q-icon name="search" class="cursor-pointer" color="primary" @click="showDg" />
|
||||||
</template>
|
</template>
|
||||||
</q-field>
|
</q-field>
|
||||||
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" widht="400px">
|
<show-dialog v-model:visible="show" name="フィールド一覧" @close="closeDg" min-width="400px">
|
||||||
<field-select ref="appDg" name="フィールド" type="single" :appId="store.appInfo?.appId"></field-select>
|
<template v-slot:toolbar>
|
||||||
|
<q-input dense debounce="300" v-model="filter" placeholder="検索" clearable>
|
||||||
|
<template v-slot:before>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
<field-select ref="appDg" name="フィールド" :type="selectType" :appId="store.appInfo?.appId" :selectedFields="selectedFields" :fieldTypes="fieldTypes" :filter="filter"></field-select>
|
||||||
</show-dialog>
|
</show-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -54,6 +60,14 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
selectType:{
|
||||||
|
type:String,
|
||||||
|
default:'single'
|
||||||
|
},
|
||||||
|
fieldTypes:{
|
||||||
|
type:Array,
|
||||||
|
default:()=>[]
|
||||||
|
},
|
||||||
hint: {
|
hint: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
@@ -62,16 +76,35 @@ export default defineComponent({
|
|||||||
type: Object,
|
type: Object,
|
||||||
default: null
|
default: null
|
||||||
},
|
},
|
||||||
|
//例:[val=>!!val ||'入力してください']
|
||||||
|
rules: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
required: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
requiredMessage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const appDg = ref();
|
const appDg = ref();
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
const selectedField = ref(props.modelValue);
|
const selectedField = ref(props.modelValue);
|
||||||
|
const selectedFields =computed(()=>!selectedField.value?[]: [selectedField.value]);
|
||||||
const store = useFlowEditorStore();
|
const store = useFlowEditorStore();
|
||||||
const isSelected = computed(() => {
|
const isSelected = computed(() => {
|
||||||
return selectedField.value !== null && typeof selectedField.value === 'object' && ('name' in selectedField.value)
|
return selectedField.value !== null && typeof selectedField.value === 'object' && ('name' in selectedField.value)
|
||||||
});
|
});
|
||||||
|
//ルール設定
|
||||||
|
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||||
|
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||||
|
const requiredExp = props.required ? [((val: any) => (!!val && typeof val==='object' && !!val.name) || errmsg)] : [];
|
||||||
|
const rulesExp = [...requiredExp, ...customExp];
|
||||||
|
|
||||||
const showDg = () => {
|
const showDg = () => {
|
||||||
show.value = true;
|
show.value = true;
|
||||||
@@ -94,7 +127,10 @@ export default defineComponent({
|
|||||||
showDg,
|
showDg,
|
||||||
closeDg,
|
closeDg,
|
||||||
selectedField,
|
selectedField,
|
||||||
isSelected
|
isSelected,
|
||||||
|
filter:ref(''),
|
||||||
|
selectedFields,
|
||||||
|
rulesExp
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,10 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<div v-bind="$attrs">
|
||||||
<q-input :label="displayName" v-model="inputValue" label-color="primary"
|
<q-input :label="displayName" v-model="inputValue" label-color="primary" :placeholder="placeholder" stack-label
|
||||||
:placeholder="placeholder" stack-label
|
:rules="rulesExp" :maxlength="maxLength">
|
||||||
:rules="rulesExp"
|
|
||||||
:maxlength="maxLength"
|
|
||||||
>
|
|
||||||
<template v-slot:append v-if="hint !== ''">
|
<template v-slot:append v-if="hint !== ''">
|
||||||
<q-icon name="help" size="22px" color="blue-8">
|
<q-icon name="help" size="22px" color="blue-8">
|
||||||
<q-tooltip class="bg-yellow-2 text-black shadow-4" anchor="bottom right">
|
<q-tooltip class="bg-yellow-2 text-black shadow-4" anchor="bottom right">
|
||||||
@@ -17,8 +14,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { kMaxLength } from 'buffer';
|
import { defineComponent, ref, watchEffect, computed } from 'vue';
|
||||||
import { defineComponent, ref, watchEffect } from 'vue';
|
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'InputText',
|
name: 'InputText',
|
||||||
@@ -36,31 +32,69 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
fieldTypes:{
|
||||||
|
type:Array,
|
||||||
|
default:()=>[]
|
||||||
|
},
|
||||||
hint: {
|
hint: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
maxLength:{
|
maxLength: {
|
||||||
type: Number,
|
type: Number,
|
||||||
default:undefined
|
default: undefined
|
||||||
},
|
},
|
||||||
//例:[val=>!!val ||'入力してください']
|
//例:[val=>!!val ||'入力してください']
|
||||||
rules:{
|
rules: {
|
||||||
type:String,
|
type: String,
|
||||||
default:undefined
|
default: undefined
|
||||||
|
},
|
||||||
|
required:{
|
||||||
|
type:Boolean,
|
||||||
|
default:false
|
||||||
|
},
|
||||||
|
requiredMessage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
},
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: String,
|
type: null as any,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const inputValue = ref(props.modelValue);
|
const inputValue = computed({
|
||||||
const rulesExp = props.rules===undefined?null : eval(props.rules);
|
get: () => {
|
||||||
watchEffect(() => {
|
if (props.modelValue !== null && typeof props.modelValue === 'object' && 'name' in props.modelValue) {
|
||||||
emit('update:modelValue', inputValue.value);
|
return props.modelValue.name;
|
||||||
|
} else {
|
||||||
|
return props.modelValue;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
set: (val) => {
|
||||||
|
if (props.name === 'verName') {
|
||||||
|
// return props.modelValue.name;
|
||||||
|
emit('update:modelValue', { name: val });
|
||||||
|
} else {
|
||||||
|
emit('update:modelValue', val);
|
||||||
|
}
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
// const inputValue = ref(props.modelValue);
|
||||||
|
|
||||||
|
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||||
|
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||||
|
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
||||||
|
const rulesExp=[...requiredExp,...customExp];
|
||||||
|
// const finalValue = computed(() => {
|
||||||
|
// return props.name !== 'verName' ? inputValue.value : {
|
||||||
|
// name: inputValue.value,
|
||||||
|
// };
|
||||||
|
// });
|
||||||
|
// watchEffect(() => {
|
||||||
|
// emit('update:modelValue', finalValue);
|
||||||
|
// });
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputValue,
|
inputValue,
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<div v-bind="$attrs">
|
||||||
<q-input :label="displayName" label-color="primary" v-model="inputValue" :placeholder="placeholder" autogrow
|
<q-input :label="displayName" label-color="primary" v-model="inputValue"
|
||||||
stack-label />
|
:placeholder="placeholder"
|
||||||
|
:rules="rulesExp"
|
||||||
|
autogrow
|
||||||
|
stack-label />
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
@@ -32,17 +35,34 @@ export default defineComponent({
|
|||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: '',
|
||||||
},
|
},
|
||||||
|
//例:[val=>!!val ||'入力してください']
|
||||||
|
rules: {
|
||||||
|
type: String,
|
||||||
|
default: undefined
|
||||||
|
},
|
||||||
|
required:{
|
||||||
|
type:Boolean,
|
||||||
|
default:false
|
||||||
|
},
|
||||||
|
requiredMessage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const inputValue = ref(props.modelValue);
|
const inputValue = ref(props.modelValue);
|
||||||
|
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||||
|
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||||
|
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
||||||
|
const rulesExp=[...requiredExp,...customExp];
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
emit('update:modelValue', inputValue.value);
|
emit('update:modelValue', inputValue.value);
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
inputValue,
|
inputValue,
|
||||||
|
rulesExp
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -49,6 +49,14 @@ export default defineComponent({
|
|||||||
type:String,
|
type:String,
|
||||||
default:undefined
|
default:undefined
|
||||||
},
|
},
|
||||||
|
required:{
|
||||||
|
type:Boolean,
|
||||||
|
default:false
|
||||||
|
},
|
||||||
|
requiredMessage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
type: [Number , String],
|
type: [Number , String],
|
||||||
default: undefined
|
default: undefined
|
||||||
@@ -57,23 +65,10 @@ export default defineComponent({
|
|||||||
|
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const numValue = ref(props.modelValue);
|
const numValue = ref(props.modelValue);
|
||||||
const rulesExp = props.rules===undefined?null : eval(props.rules);
|
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||||
const isError = computed(()=>{
|
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}が必須です。`;
|
||||||
const val = numValue.value;
|
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
||||||
if (val === undefined) {
|
const rulesExp=[...requiredExp,...customExp];
|
||||||
return false;
|
|
||||||
}
|
|
||||||
const numVal = typeof val === "string" ? parseInt(val) : val;
|
|
||||||
// Ensure parsed value is a valid number
|
|
||||||
if (isNaN(numVal)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
// Check against min and max boundaries, if defined
|
|
||||||
if ((props.min !== undefined && numVal < props.min) || (props.max !== undefined && numVal > props.max)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
watchEffect(()=>{
|
watchEffect(()=>{
|
||||||
emit("update:modelValue",numValue.value);
|
emit("update:modelValue",numValue.value);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<div v-for="(item, index) in properties" :key="index" >
|
<div v-for="(item, index) in properties" :key="index" >
|
||||||
<component :is="item.component" v-bind="item.props" :connectProps="connectProps(item.props)" v-model="item.props.modelValue"></component>
|
<component :is="item.component" v-bind="item.props" :context="properties" :connectProps="connectProps(item.props)" v-model="item.props.modelValue"></component>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -21,6 +21,10 @@ import ConditionInput from '../right/ConditionInput.vue';
|
|||||||
import EventSetter from '../right/EventSetter.vue';
|
import EventSetter from '../right/EventSetter.vue';
|
||||||
import ColorPicker from './ColorPicker.vue';
|
import ColorPicker from './ColorPicker.vue';
|
||||||
import NumInput from './NumInput.vue';
|
import NumInput from './NumInput.vue';
|
||||||
|
import DataProcessing from './DataProcessing.vue';
|
||||||
|
import DataMapping from './DataMapping.vue';
|
||||||
|
import AppSelect from './AppSelect.vue';
|
||||||
|
import CascadingDropDown from './CascadingDropDown.vue';
|
||||||
import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes';
|
import { IActionNode,IActionProperty,IProp } from 'src/types/ActionTypes';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
@@ -35,7 +39,11 @@ export default defineComponent({
|
|||||||
ConditionInput,
|
ConditionInput,
|
||||||
EventSetter,
|
EventSetter,
|
||||||
ColorPicker,
|
ColorPicker,
|
||||||
NumInput
|
NumInput,
|
||||||
|
DataProcessing,
|
||||||
|
DataMapping,
|
||||||
|
AppSelect,
|
||||||
|
CascadingDropDown
|
||||||
},
|
},
|
||||||
props: {
|
props: {
|
||||||
nodeProps: {
|
nodeProps: {
|
||||||
@@ -50,7 +58,7 @@ export default defineComponent({
|
|||||||
setup(props, context) {
|
setup(props, context) {
|
||||||
const properties=ref(props.nodeProps);
|
const properties=ref(props.nodeProps);
|
||||||
const connectProps=(props:IProp)=>{
|
const connectProps=(props:IProp)=>{
|
||||||
const connProps:any={};
|
const connProps:any={context:properties};
|
||||||
if(props && "connectProps" in props && props.connectProps!=undefined){
|
if(props && "connectProps" in props && props.connectProps!=undefined){
|
||||||
for(let connProp of props.connectProps){
|
for(let connProp of props.connectProps){
|
||||||
let targetProp = properties.value.find((prop)=>prop.props.name===connProp.propName);
|
let targetProp = properties.value.find((prop)=>prop.props.name===connProp.propName);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
elevated
|
elevated
|
||||||
overlay
|
overlay
|
||||||
>
|
>
|
||||||
|
<q-form @submit="save" autocomplete="off" class="full-height">
|
||||||
<q-card class="column" style="max-width: 300px;min-height: 100%">
|
<q-card class="column" style="max-width: 300px;min-height: 100%">
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div class="text-h6">{{ actionNode?.subTitle }}:設定</div>
|
<div class="text-h6">{{ actionNode?.subTitle }}:設定</div>
|
||||||
@@ -21,16 +22,17 @@
|
|||||||
|
|
||||||
<q-card-actions align="right" class="bg-white text-teal">
|
<q-card-actions align="right" class="bg-white text-teal">
|
||||||
<q-btn flat label="キャンセル" @click="cancel" outline dense padding="none sm" color="primary"/>
|
<q-btn flat label="キャンセル" @click="cancel" outline dense padding="none sm" color="primary"/>
|
||||||
<q-btn flat label="更新" @click="save" outline dense padding="none sm" color="primary" />
|
<q-btn flat label="更新" type="submit" outline dense padding="none sm" color="primary" />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
</q-form>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { ref,defineComponent, PropType ,watchEffect} from 'vue'
|
import { ref,defineComponent, PropType ,watchEffect} from 'vue'
|
||||||
import PropertyList from 'components/right/PropertyList.vue';
|
import PropertyList from 'components/right/PropertyList.vue';
|
||||||
import { IActionNode } from 'src/types/ActionTypes';
|
import { IActionNode, IActionProperty } from 'src/types/ActionTypes';
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'PropertyPanel',
|
name: 'PropertyPanel',
|
||||||
components: {
|
components: {
|
||||||
@@ -47,14 +49,28 @@ import { IActionNode } from 'src/types/ActionTypes';
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
emits: [
|
emits: [
|
||||||
'update:drawerRight'
|
'update:drawerRight',
|
||||||
|
'saveActionProps'
|
||||||
],
|
],
|
||||||
setup(props,{emit}) {
|
setup(props,{emit}) {
|
||||||
const showPanel =ref(props.drawerRight);
|
const showPanel =ref(props.drawerRight);
|
||||||
const actionProps =ref(props.actionNode?.actionProps);
|
|
||||||
|
const cloneProps = (actionProps:IActionProperty[]):IActionProperty[]|null=>{
|
||||||
|
if(!actionProps){
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const json=JSON.stringify(actionProps);
|
||||||
|
return JSON.parse(json);
|
||||||
|
}
|
||||||
|
|
||||||
|
const actionProps =ref(cloneProps(props.actionNode?.actionProps));
|
||||||
|
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
|
if(showPanel.value!==undefined){
|
||||||
|
showPanel.value = props.drawerRight;
|
||||||
|
}
|
||||||
showPanel.value = props.drawerRight;
|
showPanel.value = props.drawerRight;
|
||||||
actionProps.value= props.actionNode?.actionProps;
|
actionProps.value= cloneProps(props.actionNode?.actionProps);
|
||||||
});
|
});
|
||||||
|
|
||||||
const cancel = async() =>{
|
const cancel = async() =>{
|
||||||
@@ -64,7 +80,8 @@ import { IActionNode } from 'src/types/ActionTypes';
|
|||||||
|
|
||||||
const save = async () =>{
|
const save = async () =>{
|
||||||
showPanel.value=false;
|
showPanel.value=false;
|
||||||
emit('update:drawerRight',false )
|
emit('saveActionProps', actionProps.value);
|
||||||
|
emit('update:drawerRight',false );
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,11 +1,15 @@
|
|||||||
<template>
|
<template>
|
||||||
<div v-bind="$attrs">
|
<div v-bind="$attrs">
|
||||||
<q-select v-model="selectedValue" :label="displayName" :options="options"/>
|
<q-select v-model="selectedValue" :use-chips="multiple" :label="displayName" label-color="primary"
|
||||||
|
:options="options"
|
||||||
|
stack-label
|
||||||
|
:rules="rulesExp"
|
||||||
|
:multiple="multiple"/>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { defineComponent,ref,watchEffect } from 'vue';
|
import { defineComponent,ref,watchEffect,computed } from 'vue';
|
||||||
|
|
||||||
export default defineComponent({
|
export default defineComponent({
|
||||||
name: 'SelectBox',
|
name: 'SelectBox',
|
||||||
@@ -23,20 +27,44 @@ export default defineComponent({
|
|||||||
type: Array,
|
type: Array,
|
||||||
required: true,
|
required: true,
|
||||||
},
|
},
|
||||||
|
selectType:{
|
||||||
|
type:String,
|
||||||
|
default:'',
|
||||||
|
},
|
||||||
modelValue: {
|
modelValue: {
|
||||||
|
type: [Array,String],
|
||||||
|
default: null,
|
||||||
|
},
|
||||||
|
//例:[val=>!!val ||'入力してください']
|
||||||
|
rules: {
|
||||||
type: String,
|
type: String,
|
||||||
default: '',
|
default: undefined
|
||||||
|
},
|
||||||
|
required:{
|
||||||
|
type:Boolean,
|
||||||
|
default:false
|
||||||
|
},
|
||||||
|
requiredMessage: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
setup(props, { emit }) {
|
setup(props, { emit }) {
|
||||||
const selectedValue = ref(props.modelValue);
|
const selectedValue = ref(props.modelValue);
|
||||||
|
const multiple = computed(()=>{
|
||||||
|
return props.selectType==='multiple'
|
||||||
|
});
|
||||||
watchEffect(() => {
|
watchEffect(() => {
|
||||||
emit('update:modelValue', selectedValue.value);
|
emit('update:modelValue', selectedValue.value);
|
||||||
});
|
});
|
||||||
|
const customExp = props.rules === undefined ? [] : eval(props.rules);
|
||||||
|
const errmsg = props.requiredMessage?props.requiredMessage:`${props.displayName}を選択してください。`;
|
||||||
|
const requiredExp = props.required?[((val:any)=>!!val || errmsg )]:[];
|
||||||
|
const rulesExp=[...requiredExp,...customExp];
|
||||||
return {
|
return {
|
||||||
selectedValue
|
selectedValue,
|
||||||
|
multiple,
|
||||||
|
rulesExp
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,44 +1,49 @@
|
|||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import { ActionFlow } from 'src/types/ActionTypes';
|
import { ActionFlow } from 'src/types/ActionTypes';
|
||||||
|
|
||||||
export class FlowCtrl
|
export class FlowCtrl {
|
||||||
{
|
async getFlows(appId: string): Promise<ActionFlow[]> {
|
||||||
|
const flows: ActionFlow[] = [];
|
||||||
async getFlows(appId:string):Promise<ActionFlow[]>
|
try {
|
||||||
{
|
const result = await api.get(`api/flows/${appId}`);
|
||||||
const flows:ActionFlow[]=[];
|
//console.info(result.data);
|
||||||
try{
|
if (!result.data || !Array.isArray(result.data)) {
|
||||||
const result = await api.get(`api/flows/${appId}`);
|
return [];
|
||||||
//console.info(result.data);
|
|
||||||
if(!result.data || !Array.isArray(result.data)){
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
for(const flow of result.data){
|
|
||||||
flows.push(ActionFlow.fromJSON(flow.content));
|
|
||||||
}
|
|
||||||
return flows;
|
|
||||||
}catch(error){
|
|
||||||
console.error(error);
|
|
||||||
return flows;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (const flow of result.data) {
|
||||||
|
flows.push(ActionFlow.fromJSON(flow.content));
|
||||||
|
}
|
||||||
|
return flows;
|
||||||
|
} catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
return flows;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async SaveFlow(jsonData:any):Promise<boolean>
|
async SaveFlow(jsonData: any): Promise<boolean> {
|
||||||
{
|
const result = await api.post('api/flow', jsonData);
|
||||||
const result = await api.post('api/flow',jsonData);
|
console.info(result.data);
|
||||||
console.info(result.data)
|
return true;
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* フローを更新する
|
* フローを更新する
|
||||||
* @param jsonData
|
* @param jsonData
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async UpdateFlow(jsonData:any):Promise<boolean>
|
async UpdateFlow(jsonData: any): Promise<boolean> {
|
||||||
{
|
const result = await api.put('api/flow/' + jsonData.flowid, jsonData);
|
||||||
const result = await api.put('api/flow/' + jsonData.flowid,jsonData);
|
console.info(result.data);
|
||||||
console.info(result.data)
|
return true;
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* フローを消去する
|
||||||
|
* @param flowId
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
async DeleteFlow(flowId: string): Promise<boolean> {
|
||||||
|
const result = await api.delete('api/flow/' + flowId);
|
||||||
|
console.info(result.data);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
@@ -46,12 +51,9 @@ export class FlowCtrl
|
|||||||
* @param appid
|
* @param appid
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async depoly(appid:string):Promise<boolean>
|
async depoly(appid: string): Promise<boolean> {
|
||||||
{
|
|
||||||
const result = await api.post(`api/v1/createjstokintone?app=${appid}`);
|
const result = await api.post(`api/v1/createjstokintone?app=${appid}`);
|
||||||
console.info(result.data);
|
console.info(result.data);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2,40 +2,28 @@
|
|||||||
<q-layout view="lHh Lpr lFf">
|
<q-layout view="lHh Lpr lFf">
|
||||||
<q-header elevated>
|
<q-header elevated>
|
||||||
<q-toolbar>
|
<q-toolbar>
|
||||||
<q-btn
|
<q-btn flat dense round icon="menu" aria-label="Menu" @click="toggleLeftDrawer" />
|
||||||
flat
|
|
||||||
dense
|
|
||||||
round
|
|
||||||
icon="menu"
|
|
||||||
aria-label="Menu"
|
|
||||||
@click="toggleLeftDrawer"
|
|
||||||
/>
|
|
||||||
<q-toolbar-title>
|
<q-toolbar-title>
|
||||||
{{ productName }}
|
{{ productName }}
|
||||||
<q-badge align="top" outline>V{{ version }}</q-badge>
|
<q-badge align="top" outline>V{{ version }}</q-badge>
|
||||||
</q-toolbar-title>
|
</q-toolbar-title>
|
||||||
<domain-selector></domain-selector>
|
<domain-selector></domain-selector>
|
||||||
<q-btn flat round dense icon="logout" @click="authStore.logout()"/>
|
<q-btn flat round dense icon="logout" @click="authStore.logout()" />
|
||||||
</q-toolbar>
|
</q-toolbar>
|
||||||
</q-header>
|
</q-header>
|
||||||
|
|
||||||
<q-drawer
|
<q-drawer :model-value="authStore.LeftDrawer" :show-if-above="false" bordered>
|
||||||
:model-value="authStore.toggleLeftDrawer"
|
|
||||||
:show-if-above="false"
|
|
||||||
bordered
|
|
||||||
>
|
|
||||||
<q-list>
|
<q-list>
|
||||||
<q-item-label
|
<q-item-label header>
|
||||||
header
|
メニュー
|
||||||
>
|
|
||||||
関連リンク
|
|
||||||
</q-item-label>
|
</q-item-label>
|
||||||
|
|
||||||
<EssentialLink
|
<EssentialLink v-for="link in essentialLinks" :key="link.title" v-bind="link" />
|
||||||
v-for="link in essentialLinks"
|
<div v-if="isAdmin()">
|
||||||
:key="link.title"
|
<EssentialLink v-for="link in adminLinks" :key="link.title" v-bind="link" />
|
||||||
v-bind="link"
|
</div>
|
||||||
/>
|
<EssentialLink v-for="link in domainLinks" :key="link.title" v-bind="link" />
|
||||||
|
|
||||||
</q-list>
|
</q-list>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
|
|
||||||
@@ -46,7 +34,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue';
|
import { onMounted } from 'vue';
|
||||||
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
|
import EssentialLink, { EssentialLinkProps } from 'components/EssentialLink.vue';
|
||||||
import DomainSelector from 'components/DomainSelector.vue';
|
import DomainSelector from 'components/DomainSelector.vue';
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
@@ -54,107 +42,97 @@ import { useAuthStore } from 'stores/useAuthStore';
|
|||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
const essentialLinks: EssentialLinkProps[] = [
|
const essentialLinks: EssentialLinkProps[] = [
|
||||||
{
|
{
|
||||||
title: 'ホーム',
|
title: 'ホーム',
|
||||||
caption: 'home',
|
caption: '設計書から導入する',
|
||||||
icon: 'home',
|
icon: 'home',
|
||||||
link: '/',
|
link: '/',
|
||||||
target:'_self'
|
target: '_self'
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// title: 'フローエディター',
|
||||||
|
// caption: 'イベントを設定する',
|
||||||
|
// icon: 'account_tree',
|
||||||
|
// link: '/#/FlowChart',
|
||||||
|
// target: '_self'
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
title: 'フローエディター',
|
title: 'アプリ管理',
|
||||||
caption: 'flowChart',
|
caption: 'アプリを管理する',
|
||||||
icon: 'account_tree',
|
icon: 'widgets',
|
||||||
link: '/#/FlowChart',
|
link: '/#/app',
|
||||||
target:'_self'
|
target: '_self'
|
||||||
},
|
},
|
||||||
|
// {
|
||||||
|
// title: '条件エディター',
|
||||||
|
// caption: 'condition',
|
||||||
|
// icon: 'tune',
|
||||||
|
// link: '/#/condition',
|
||||||
|
// target:'_self'
|
||||||
|
// },
|
||||||
{
|
{
|
||||||
title: '条件エディター',
|
title: '',
|
||||||
caption: 'condition',
|
isSeparator: true
|
||||||
icon: 'tune',
|
|
||||||
link: '/#/condition',
|
|
||||||
target:'_self'
|
|
||||||
},
|
},
|
||||||
{
|
// {
|
||||||
title:'',
|
// title:'Kintone ポータル',
|
||||||
isSeparator:true
|
// caption:'Kintone',
|
||||||
},
|
// icon:'cloud_queue',
|
||||||
{
|
// link:'https://mfu07rkgnb7c.cybozu.com/k/#/portal'
|
||||||
title:'Kintone ポータル',
|
// },
|
||||||
caption:'Kintone',
|
// {
|
||||||
icon:'cloud_queue',
|
// title:'CUSTOMINE',
|
||||||
link:'https://mfu07rkgnb7c.cybozu.com/k/#/portal'
|
// caption:'gusuku',
|
||||||
},
|
// link:'https://app-customine.gusuku.io/drive.html',
|
||||||
{
|
// icon:'settings_suggest'
|
||||||
title:'CUSTOMINE',
|
// },
|
||||||
caption:'gusuku',
|
// {
|
||||||
link:'https://app-customine.gusuku.io/drive.html',
|
// title:'Kintone API ドキュメント',
|
||||||
icon:'settings_suggest'
|
// caption:'Kintone API',
|
||||||
},
|
// link:'https://cybozu.dev/ja/kintone/docs/',
|
||||||
{
|
// icon:'help_outline'
|
||||||
title:'Kintone API ドキュメント',
|
// },
|
||||||
caption:'Kintone API',
|
|
||||||
link:'https://cybozu.dev/ja/kintone/docs/',
|
|
||||||
icon:'help_outline'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title:'',
|
|
||||||
isSeparator:true
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Docs',
|
|
||||||
caption: 'quasar.dev',
|
|
||||||
icon: 'school',
|
|
||||||
link: 'https://quasar.dev'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Icons',
|
|
||||||
caption: 'Material Icons',
|
|
||||||
icon: 'insert_emoticon',
|
|
||||||
link: 'https://fonts.google.com/icons?selected=Material+Icons:insert_emoticon:'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Github',
|
|
||||||
caption: 'github.com/quasarframework',
|
|
||||||
icon: 'code',
|
|
||||||
link: 'https://github.com/quasarframework'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Discord Chat Channel',
|
|
||||||
caption: 'chat.quasar.dev',
|
|
||||||
icon: 'chat',
|
|
||||||
link: 'https://chat.quasar.dev'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Forum',
|
|
||||||
caption: 'forum.quasar.dev',
|
|
||||||
icon: 'record_voice_over',
|
|
||||||
link: 'https://forum.quasar.dev'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Twitter',
|
|
||||||
caption: '@quasarframework',
|
|
||||||
icon: 'rss_feed',
|
|
||||||
link: 'https://twitter.quasar.dev'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Facebook',
|
|
||||||
caption: '@QuasarFramework',
|
|
||||||
icon: 'public',
|
|
||||||
link: 'https://facebook.quasar.dev'
|
|
||||||
},
|
|
||||||
{
|
|
||||||
title: 'Quasar Awesome',
|
|
||||||
caption: 'Community Quasar projects',
|
|
||||||
icon: 'favorite',
|
|
||||||
link: 'https://awesome.quasar.dev'
|
|
||||||
}
|
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const domainLinks: EssentialLinkProps[] = [
|
||||||
|
{
|
||||||
|
title: 'ドメイン管理',
|
||||||
|
caption: 'kintoneのドメイン設定',
|
||||||
|
icon: 'domain',
|
||||||
|
link: '/#/domain',
|
||||||
|
target: '_self'
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// title: 'ドメイン適用',
|
||||||
|
// caption: 'ユーザー使用可能なドメインの設定',
|
||||||
|
// icon: 'assignment_ind',
|
||||||
|
// link: '/#/userDomain',
|
||||||
|
// target: '_self'
|
||||||
|
// },
|
||||||
|
];
|
||||||
|
|
||||||
|
const adminLinks: EssentialLinkProps[] = [
|
||||||
|
{
|
||||||
|
title: 'ユーザー管理',
|
||||||
|
caption: 'ユーザーを管理する',
|
||||||
|
icon: 'manage_accounts',
|
||||||
|
link: '/#/user',
|
||||||
|
target: '_self'
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
const version = process.env.version;
|
const version = process.env.version;
|
||||||
const productName = process.env.productName;
|
const productName = process.env.productName;
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
authStore.toggleLeftMenu();
|
||||||
|
});
|
||||||
|
|
||||||
function toggleLeftDrawer() {
|
function toggleLeftDrawer() {
|
||||||
authStore.toggleLeftMenu();
|
authStore.toggleLeftMenu();
|
||||||
}
|
}
|
||||||
|
function isAdmin(){
|
||||||
|
const permission = authStore.permissions;
|
||||||
|
return permission === 'admin'
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
123
frontend/src/pages/AppManagement.vue
Normal file
123
frontend/src/pages/AppManagement.vue
Normal file
@@ -0,0 +1,123 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<div class="q-gutter-sm row items-start">
|
||||||
|
<q-breadcrumbs>
|
||||||
|
<q-breadcrumbs-el icon="widgets" label="アプリ管理" />
|
||||||
|
</q-breadcrumbs>
|
||||||
|
</div>
|
||||||
|
<q-table title="Treats" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
|
||||||
|
|
||||||
|
<template v-slot:top>
|
||||||
|
<q-btn disabled color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||||
|
<q-space />
|
||||||
|
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
<template v-slot:body-cell-url="prop">
|
||||||
|
<q-td :props="prop">
|
||||||
|
<a :href="prop.row.url" target="_blank" :title="prop.row.name" >
|
||||||
|
{{ prop.row.url }}
|
||||||
|
</a>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
<template v-slot:body-cell-actions="p">
|
||||||
|
<q-td :props="p">
|
||||||
|
<q-btn-group flat>
|
||||||
|
<q-btn flat color="primary" padding="xs" size="1em" icon="edit_note" @click="editFlow(p.row)" />
|
||||||
|
<q-btn disabled flat color="primary" padding="xs" size="1em" icon="history" @click="showHistory(p.row)" />
|
||||||
|
<q-btn disabled flat color="negative" padding="xs" size="1em" icon="delete_outline" @click="removeRow(p.row)" />
|
||||||
|
</q-btn-group>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
</q-table>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted, watch, reactive } from 'vue';
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
|
import { router } from 'src/router';
|
||||||
|
import { date } from 'quasar'
|
||||||
|
import { IManagedApp } from 'src/types/AppTypes';
|
||||||
|
|
||||||
|
interface IAppDisplay{
|
||||||
|
id:string;
|
||||||
|
name:string;
|
||||||
|
url:string;
|
||||||
|
user:string;
|
||||||
|
version:string;
|
||||||
|
updatetime:string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const numberStringSorting = (a: string, b: string) => parseInt(a, 10) - parseInt(b, 10);
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ name: 'id', label: 'アプリID', field: 'id', align: 'left', sortable: true, sort: numberStringSorting },
|
||||||
|
{ name: 'name', label: 'アプリ名', field: 'name', align: 'left', sortable: true },
|
||||||
|
{ name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true },
|
||||||
|
{ name: 'user', label: '最後更新者', field: 'user', align: 'left', sortable: true},
|
||||||
|
{ name: 'updatetime', label: '最後更新日', field: 'updatetime', align: 'left', sortable: true},
|
||||||
|
{ name: 'version', label: 'バージョン', field: 'version', align: 'left', sortable: true},
|
||||||
|
{ name: 'actions', label: '操作', field: 'actions' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
||||||
|
const loading = ref(false);
|
||||||
|
const filter = ref('');
|
||||||
|
const rows = ref<IAppDisplay[]>([]);
|
||||||
|
const store = useFlowEditorStore();
|
||||||
|
|
||||||
|
const getApps = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const result = await api.get('api/apps');
|
||||||
|
rows.value = result.data.map((item: IManagedApp) => {
|
||||||
|
return {
|
||||||
|
id: item.appid,
|
||||||
|
name: item.appname,
|
||||||
|
url: `${item.domainurl}/k/${item.appid}`,
|
||||||
|
user: `${item.user.first_name} ${item.user.last_name}` ,
|
||||||
|
updatetime:date.formatDate(item.update_time, 'YYYY/MM/DD HH:mm'),
|
||||||
|
version: Number(item.version)
|
||||||
|
}
|
||||||
|
}).sort((a: IAppDisplay, b: IAppDisplay) => numberStringSorting(a.id, b.id)); // set default order
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
authStore.setLeftMenu(false);
|
||||||
|
await getApps();
|
||||||
|
});
|
||||||
|
|
||||||
|
watch(() => authStore.currentDomain.id, async () => {
|
||||||
|
await getApps();
|
||||||
|
});
|
||||||
|
|
||||||
|
const addRow = () => {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeRow = (app:IAppDisplay) => {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const showHistory = (app:IAppDisplay) => {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
const editFlow = (app:IAppDisplay) => {
|
||||||
|
store.setApp({
|
||||||
|
appId: app.id,
|
||||||
|
name: app.name
|
||||||
|
});
|
||||||
|
store.selectFlow(undefined);
|
||||||
|
router.push('/FlowChart/' + app.id);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
@@ -3,11 +3,7 @@
|
|||||||
<q-layout container class="absolute-full shadow-2 rounded-borders">
|
<q-layout container class="absolute-full shadow-2 rounded-borders">
|
||||||
<div class="q-pa-sm q-gutter-sm ">
|
<div class="q-pa-sm q-gutter-sm ">
|
||||||
<q-drawer side="left" :overlay="true" bordered v-model="drawerLeft" :show-if-above="false" elevated>
|
<q-drawer side="left" :overlay="true" bordered v-model="drawerLeft" :show-if-above="false" elevated>
|
||||||
<div class="flex-center fixed-top app-selector">
|
<div class="flex-center absolute-full" style="padding:15px">
|
||||||
<AppSelector />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex-center absolute-full" style="padding-top:65px;padding-left:15px;padding-right:15px;">
|
|
||||||
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '0' }">
|
<q-scroll-area class="fit" :horizontal-thumb-style="{ opacity: '0' }">
|
||||||
<EventTree />
|
<EventTree />
|
||||||
</q-scroll-area>
|
</q-scroll-area>
|
||||||
@@ -16,14 +12,59 @@
|
|||||||
<div class="flex-center fixed-bottom bg-grey-3 q-pa-md row ">
|
<div class="flex-center fixed-bottom bg-grey-3 q-pa-md row ">
|
||||||
<q-btn color="secondary" glossy label="デプロイ" @click="onDeploy" icon="sync" :loading="deployLoading" />
|
<q-btn color="secondary" glossy label="デプロイ" @click="onDeploy" icon="sync" :loading="deployLoading" />
|
||||||
<q-space></q-space>
|
<q-space></q-space>
|
||||||
<q-btn color="primary" label="保存" @click="onSaveFlow" icon="save" :loading="saveLoading" />
|
<q-btn-dropdown color="primary" label="保存" icon="save" :loading="saveLoading" >
|
||||||
|
<q-list>
|
||||||
|
<q-item clickable v-close-popup @click="onSaveVersion">
|
||||||
|
<q-item-section avatar >
|
||||||
|
<q-icon name="history"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>新バージョン保存</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item clickable v-close-popup @click="onSaveFlow">
|
||||||
|
<q-item-section avatar >
|
||||||
|
<q-icon name="save" color="primary"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>選択中フローの保存</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item clickable v-close-popup @click="onSaveAllFlow">
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-icon name="collections_bookmark" color="accent"></q-icon>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>一括保存</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
</q-list>
|
||||||
|
</q-btn-dropdown>
|
||||||
</div>
|
</div>
|
||||||
</q-drawer>
|
</q-drawer>
|
||||||
</div>
|
</div>
|
||||||
<q-btn flat dense round
|
<q-btn flat dense round
|
||||||
:icon="drawerLeft?'keyboard_double_arrow_left':'keyboard_double_arrow_right'"
|
:icon="drawerLeft?'keyboard_double_arrow_left':'keyboard_double_arrow_right'"
|
||||||
:style="[drawerLeft?{'left':'300px'}:{'left':'0px'}]"
|
:style="{'left': fixedLeftPosition}"
|
||||||
@click="drawerLeft=!drawerLeft" class="expand" />
|
@click="drawerLeft=!drawerLeft" class="expand" />
|
||||||
|
<q-breadcrumbs v-if="store.appInfo" class="fixed q-pl-md"
|
||||||
|
:style="{'left': fixedLeftPosition}">
|
||||||
|
<q-breadcrumbs-el icon="widgets" label="アプリ管理" to="/app" />
|
||||||
|
<q-breadcrumbs-el>
|
||||||
|
<template v-slot>
|
||||||
|
<a class="full-width" :href="!store.appInfo?'':`${authStore.currentDomain.kintoneUrl}/k/${store.appInfo?.appId}`" target="_blank" title="Kiontoneへ">
|
||||||
|
{{ store.appInfo?.name }}
|
||||||
|
<q-icon
|
||||||
|
class="q-ma-xs"
|
||||||
|
name="open_in_new"
|
||||||
|
color="grey-9"
|
||||||
|
/>
|
||||||
|
</a>
|
||||||
|
</template>
|
||||||
|
</q-breadcrumbs-el>
|
||||||
|
</q-breadcrumbs>
|
||||||
<div class="q-pa-md q-gutter-sm" :style="{minWidth: minPanelWidth}">
|
<div class="q-pa-md q-gutter-sm" :style="{minWidth: minPanelWidth}">
|
||||||
<div class="flowchart" v-if="store.currentFlow" :style="[drawerLeft?{paddingLeft:'300px'}:{}]">
|
<div class="flowchart" v-if="store.currentFlow" :style="[drawerLeft?{paddingLeft:'300px'}:{}]">
|
||||||
<node-item v-if="rootNode!==undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode"
|
<node-item v-if="rootNode!==undefined" :key="rootNode.id" :isSelected="rootNode === store.activeNode"
|
||||||
@@ -31,7 +72,7 @@
|
|||||||
@deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item>
|
@deleteNode="onDeleteNode" @deleteAllNextNodes="onDeleteAllNextNodes" @copyFlow="onCopyFlow"></node-item>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<PropertyPanel :actionNode="store.activeNode" v-model:drawerRight="drawerRight"></PropertyPanel>
|
<PropertyPanel :actionNode="store.activeNode" v-model:drawerRight="drawerRight" @save-action-props="onSaveActionProps"></PropertyPanel>
|
||||||
</q-layout>
|
</q-layout>
|
||||||
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg" min-width="500px" min-height="500px">
|
<ShowDialog v-model:visible="showAddAction" name="アクション" @close="closeDg" min-width="500px" min-height="500px">
|
||||||
<template v-slot:toolbar>
|
<template v-slot:toolbar>
|
||||||
@@ -41,33 +82,48 @@
|
|||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</template>
|
</template>
|
||||||
<action-select ref="appDg" name="model" :filter="filter" type="single"></action-select>
|
<action-select ref="appDg" name="model" :filter="filter" type="single" @clearFilter="onClearFilter" ></action-select>
|
||||||
</ShowDialog>
|
</ShowDialog>
|
||||||
|
<!-- save version dialog -->
|
||||||
|
<ShowDialog v-model:visible="saveVersionAction" name="新バージョン保存" @close="closeSaveVersionDg" min-width="500px">
|
||||||
|
<version-input v-model="versionInfo" />
|
||||||
|
</ShowDialog>
|
||||||
|
<q-inner-loading
|
||||||
|
:showing="initLoading"
|
||||||
|
color="primary"
|
||||||
|
label="読み込み中..."
|
||||||
|
/>
|
||||||
</q-page>
|
</q-page>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, reactive, computed, onMounted } from 'vue';
|
import { ref, reactive, computed, onMounted } from 'vue';
|
||||||
|
import { useRoute } from 'vue-router';
|
||||||
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes';
|
import { IActionNode, ActionNode, IActionFlow, ActionFlow, RootAction, IActionProperty } from 'src/types/ActionTypes';
|
||||||
|
import { IManagedApp, IVersionInfo } from 'src/types/AppTypes';
|
||||||
import { storeToRefs } from 'pinia';
|
import { storeToRefs } from 'pinia';
|
||||||
import { useFlowEditorStore } from 'stores/flowEditor';
|
import { useFlowEditorStore } from 'stores/flowEditor';
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
|
||||||
import NodeItem from 'src/components/main/NodeItem.vue';
|
import NodeItem from 'src/components/main/NodeItem.vue';
|
||||||
import ShowDialog from 'components/ShowDialog.vue';
|
import ShowDialog from 'components/ShowDialog.vue';
|
||||||
import ActionSelect from 'components/ActionSelect.vue';
|
import ActionSelect from 'components/ActionSelect.vue';
|
||||||
import PropertyPanel from 'components/right/PropertyPanel.vue';
|
import PropertyPanel from 'components/right/PropertyPanel.vue';
|
||||||
import AppSelector from 'components/left/AppSelector.vue';
|
|
||||||
import EventTree from 'components/left/EventTree.vue';
|
import EventTree from 'components/left/EventTree.vue';
|
||||||
|
import VersionInput from 'components/dialog/VersionInput.vue';
|
||||||
import { FlowCtrl } from '../control/flowctrl';
|
import { FlowCtrl } from '../control/flowctrl';
|
||||||
import { useQuasar } from 'quasar';
|
import { useQuasar } from 'quasar';
|
||||||
|
|
||||||
const deployLoading = ref(false);
|
const deployLoading = ref(false);
|
||||||
const saveLoading = ref(false);
|
const saveLoading = ref(false);
|
||||||
|
const initLoading = ref(true);
|
||||||
const drawerLeft = ref(false);
|
const drawerLeft = ref(false);
|
||||||
|
const versionInfo = ref<IVersionInfo>();
|
||||||
const $q = useQuasar();
|
const $q = useQuasar();
|
||||||
const store = useFlowEditorStore();
|
const store = useFlowEditorStore();
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
const appDg = ref();
|
const appDg = ref();
|
||||||
const prevNodeIfo = ref({
|
const prevNodeIfo = ref({
|
||||||
@@ -76,13 +132,11 @@ const prevNodeIfo = ref({
|
|||||||
});
|
});
|
||||||
// const refFlow = ref<ActionFlow|null>(null);
|
// const refFlow = ref<ActionFlow|null>(null);
|
||||||
const showAddAction = ref(false);
|
const showAddAction = ref(false);
|
||||||
|
const saveVersionAction = ref(false);
|
||||||
const drawerRight = ref(false);
|
const drawerRight = ref(false);
|
||||||
const filter=ref("");
|
const filter=ref("");
|
||||||
const model = ref("");
|
const model = ref("");
|
||||||
const addActionNode = (action: IActionNode) => {
|
|
||||||
// refFlow.value?.actionNodes.push(action);
|
|
||||||
store.currentFlow?.actionNodes.push(action);
|
|
||||||
}
|
|
||||||
const rootNode = computed(()=>{
|
const rootNode = computed(()=>{
|
||||||
return store.currentFlow?.getRoot();
|
return store.currentFlow?.getRoot();
|
||||||
});
|
});
|
||||||
@@ -94,6 +148,9 @@ const minPanelWidth=computed(()=>{
|
|||||||
return "300px";
|
return "300px";
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
const fixedLeftPosition = computed(()=>{
|
||||||
|
return drawerLeft.value?"300px":"0px";
|
||||||
|
});
|
||||||
|
|
||||||
const addNode = (node: IActionNode, inputPoint: string) => {
|
const addNode = (node: IActionNode, inputPoint: string) => {
|
||||||
if (drawerRight.value) {
|
if (drawerRight.value) {
|
||||||
@@ -136,7 +193,7 @@ const onDeleteAllNextNodes = (node: IActionNode) => {
|
|||||||
}
|
}
|
||||||
const closeDg = (val: any) => {
|
const closeDg = (val: any) => {
|
||||||
console.log("Dialog closed->", val);
|
console.log("Dialog closed->", val);
|
||||||
if (val == 'OK') {
|
if (val == 'OK' && appDg?.value?.selected?.length > 0) {
|
||||||
const data = appDg.value.selected[0];
|
const data = appDg.value.selected[0];
|
||||||
const actionProps = JSON.parse(data.property);
|
const actionProps = JSON.parse(data.property);
|
||||||
const outputPoint = JSON.parse(data.outputPoints);
|
const outputPoint = JSON.parse(data.outputPoints);
|
||||||
@@ -193,13 +250,38 @@ const onDeploy = async () => {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const onSaveActionProps=(props:IActionProperty[])=>{
|
||||||
|
if(store.activeNode){
|
||||||
|
store.activeNode.actionProps=props;
|
||||||
|
$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
caption: "通知",
|
||||||
|
message: `${store.activeNode?.subTitle}の属性を設定しました。(保存はされていません)`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSaveVersion = async () => {
|
||||||
|
versionInfo.value = {
|
||||||
|
id: '1' // TODO
|
||||||
|
}
|
||||||
|
saveVersionAction.value = true;
|
||||||
|
// await onSaveAllFlow();
|
||||||
|
}
|
||||||
|
|
||||||
|
const closeSaveVersionDg = (val: 'OK'|'CANCEL') => {
|
||||||
|
if (val == 'OK') {
|
||||||
|
console.log(versionInfo.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const onSaveFlow = async () => {
|
const onSaveFlow = async () => {
|
||||||
const targetFlow = store.selectedFlow;
|
const targetFlow = store.selectedFlow;
|
||||||
if (targetFlow === undefined) {
|
if (targetFlow === undefined) {
|
||||||
$q.notify({
|
$q.notify({
|
||||||
type: 'negative',
|
type: 'negative',
|
||||||
caption: "エラー",
|
caption: 'エラー',
|
||||||
message: `編集中のフローがありません。`
|
message: `選択中のフローがありません。`
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -221,38 +303,76 @@ const onSaveFlow = async () => {
|
|||||||
message: `${targetFlow.getRoot()?.subTitle}のフローの設定の保存が失敗しました。`
|
message: `${targetFlow.getRoot()?.subTitle}のフローの設定の保存が失敗しました。`
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* すべてフローの設定を保存する
|
||||||
|
*/
|
||||||
|
const onSaveAllFlow= async ()=>{
|
||||||
|
try{
|
||||||
|
const targetFlows = store.eventTree.findAllFlows();
|
||||||
|
if (!targetFlows || targetFlows.length === 0 ) {
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
caption: 'エラー',
|
||||||
|
message: `設定されたフローがありません。`
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
saveLoading.value = true;
|
||||||
|
for(const flow of targetFlows ){
|
||||||
|
const isNew = flow.id === '';
|
||||||
|
if(isNew && flow.actionNodes.length===1){
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await store.saveFlow(flow);
|
||||||
|
}
|
||||||
|
$q.notify({
|
||||||
|
type: 'positive',
|
||||||
|
caption: "通知",
|
||||||
|
message: `すべてのフロー設定を保存しました。`
|
||||||
|
});
|
||||||
|
saveLoading.value = false;
|
||||||
|
}catch (error) {
|
||||||
|
console.error(error);
|
||||||
|
saveLoading.value = false;
|
||||||
|
$q.notify({
|
||||||
|
type: 'negative',
|
||||||
|
caption: "エラー",
|
||||||
|
message: `フローの設定の保存が失敗しました。`
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const fetchData = async () => {
|
const fetchData = async () => {
|
||||||
|
initLoading.value = true;
|
||||||
|
if (store.appInfo === undefined && route?.params?.id !== undefined) {
|
||||||
|
const { appid, appname } = await fetchAppById(route.params.id as string);
|
||||||
|
store.setApp({
|
||||||
|
appId: appid,
|
||||||
|
name: appname
|
||||||
|
});
|
||||||
|
};
|
||||||
|
await store.loadFlow();
|
||||||
|
initLoading.value = false
|
||||||
drawerLeft.value = true;
|
drawerLeft.value = true;
|
||||||
if (store.appInfo === undefined) return;
|
}
|
||||||
const flowCtrl = new FlowCtrl();
|
|
||||||
const actionFlows = await flowCtrl.getFlows(store.appInfo?.appId);
|
const fetchAppById = async(id: string) => {
|
||||||
if (actionFlows && actionFlows.length > 0) {
|
const result = await api.get('api/apps');
|
||||||
store.setFlows(actionFlows);
|
return result.data.find((item: IManagedApp) => item.appid === id ) as IManagedApp;
|
||||||
}
|
}
|
||||||
if (actionFlows && actionFlows.length == 1) {
|
|
||||||
store.selectFlow(actionFlows[0]);
|
const onClearFilter=()=>{
|
||||||
}
|
filter.value='';
|
||||||
const root = actionFlows[0].getRoot();
|
|
||||||
if (root) {
|
|
||||||
store.setActiveNode(root);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
authStore.toggleLeftMenu();
|
authStore.setLeftMenu(false);
|
||||||
fetchData();
|
fetchData();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss">
|
<style lang="scss">
|
||||||
.app-selector {
|
|
||||||
padding: 15px;
|
|
||||||
z-index: 999;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flowchart {
|
.flowchart {
|
||||||
padding-top: 10px;
|
padding-top: 10px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@
|
|||||||
<q-btn :label="model+'選択'" color="primary" @click="showDg()" />
|
<q-btn :label="model+'選択'" color="primary" @click="showDg()" />
|
||||||
<show-dialog v-model:visible="show" :name="model" @close="closeDg" width="400px">
|
<show-dialog v-model:visible="show" :name="model" @close="closeDg" width="400px">
|
||||||
<template v-if="model=='アプリ'">
|
<template v-if="model=='アプリ'">
|
||||||
<app-select ref="appDg" :name="model" type="single"></app-select>
|
<app-select-box ref="appDg" :name="model" type="single"></app-select-box>
|
||||||
</template>
|
</template>
|
||||||
<template v-if="model=='フィールド'">
|
<template v-if="model=='フィールド'">
|
||||||
<field-select ref="appDg" :name="model" type="multiple" :appId="1"></field-select>
|
<field-select ref="appDg" :name="model" type="multiple" :appId="1"></field-select>
|
||||||
@@ -42,7 +42,7 @@
|
|||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import ShowDialog from 'components/ShowDialog.vue';
|
import ShowDialog from 'components/ShowDialog.vue';
|
||||||
import AppSelect from 'components/AppSelect.vue';
|
import AppSelectBox from 'components/AppSelectBox.vue';
|
||||||
import FieldSelect from 'components/FieldSelect.vue';
|
import FieldSelect from 'components/FieldSelect.vue';
|
||||||
import ActionSelect from 'components/ActionSelect.vue';
|
import ActionSelect from 'components/ActionSelect.vue';
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|||||||
@@ -1,54 +1,95 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md">
|
<div class="q-pa-md">
|
||||||
<q-table title="Treats" :rows="rows" :columns="columns" row-key="id" selection="single" :filter="filter"
|
<div class="q-gutter-sm row items-start">
|
||||||
:loading="loading" v-model:selected="selected">
|
<q-breadcrumbs>
|
||||||
|
<q-breadcrumbs-el icon="domain" label="ドメイン管理" />
|
||||||
|
</q-breadcrumbs>
|
||||||
|
</div>
|
||||||
|
<q-table title="Treats" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading" :pagination="pagination">
|
||||||
|
|
||||||
<template v-slot:top>
|
<template v-slot:top>
|
||||||
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
|
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||||
<q-btn class="q-ml-sm" color="primary" :disable="loading" label="編集" @click="editRow" />
|
|
||||||
<q-btn class="q-ml-sm" color="primary" :disable="loading" label="削除" @click="removeRow" />
|
|
||||||
<q-space />
|
<q-space />
|
||||||
<q-input borderless dense debounce="300" color="primary" v-model="filter">
|
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||||
<template v-slot:append>
|
<template v-slot:append>
|
||||||
<q-icon name="search" />
|
<q-icon name="search" />
|
||||||
</template>
|
</template>
|
||||||
</q-input>
|
</q-input>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body-cell-actions="p">
|
||||||
|
<q-td :props="p">
|
||||||
|
<q-btn-group flat>
|
||||||
|
<q-btn flat color="primary" padding="xs" size="1em" icon="edit_note" @click="editRow(p.row)" />
|
||||||
|
<q-btn flat color="negative" padding="xs" size="1em" icon="delete_outline" @click="removeRow(p.row)" />
|
||||||
|
</q-btn-group>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
</q-table>
|
</q-table>
|
||||||
|
|
||||||
<q-dialog :model-value="show" persistent>
|
<q-dialog :model-value="show" persistent>
|
||||||
<q-card style="min-width: 400px">
|
<q-card style="min-width: 36em">
|
||||||
<q-card-section>
|
<q-form class="q-gutter-md" @submit="onSubmit" autocomplete="off">
|
||||||
<div class="text-h6">Kintone Account</div>
|
<q-card-section>
|
||||||
</q-card-section>
|
<div class="text-h6 q-ma-sm">Kintone Account</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-section class="q-pt-none">
|
<q-card-section class="q-pt-none q-mt-none">
|
||||||
<q-form class="q-gutter-md">
|
<div class="q-gutter-lg">
|
||||||
<q-input filled v-model="tenantid" label="Tenant" hint="Tenant ID" lazy-rules
|
|
||||||
:rules="[val => val && val.length > 0 || 'Please type something']" />
|
|
||||||
|
|
||||||
<q-input filled v-model="name" label="Your name *" hint="Kintone envirment name" lazy-rules
|
|
||||||
:rules="[val => val && val.length > 0 || 'Please type something']" />
|
|
||||||
|
|
||||||
<q-input filled type="url" v-model="url" label="Kintone url" hint="Kintone domain address" lazy-rules
|
<q-input filled v-model="tenantid" label="テナントID" hint="テナントIDを入力してください。" lazy-rules
|
||||||
:rules="[val => val && val.length > 0, isDomain || 'Please type something']" />
|
:rules="[val => val && val.length > 0 || 'テナントIDを入力してください。']" />
|
||||||
|
|
||||||
<q-input filled v-model="kintoneuser" label="Login user " hint="Kintone user name" lazy-rules
|
<q-input filled v-model="name" label="環境名 *" hint="kintoneの環境名を入力してください" lazy-rules
|
||||||
:rules="[val => val && val.length > 0 || 'Please type something']" />
|
:rules="[val => val && val.length > 0 || 'kintoneの環境名を入力してください。']" />
|
||||||
|
|
||||||
<q-input v-model="kintonepwd" filled :type="isPwd ? 'password' : 'text'" hint="Password with toggle"
|
<q-input filled type="url" v-model="url" label="Kintone url" hint="KintoneのURLを入力してください" lazy-rules
|
||||||
label="User password">
|
:rules="[val => val && val.length > 0, isDomain || 'KintoneのURLを入力してください']" />
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer" @click="isPwd = !isPwd" />
|
<q-input filled v-model="kintoneuser" label="ログイン名 *" hint="Kintoneのログイン名を入力してください" lazy-rules
|
||||||
</template>
|
:rules="[val => val && val.length > 0 || 'Kintoneのログイン名を入力してください']" autocomplete="new-password" />
|
||||||
</q-input>
|
|
||||||
</q-form>
|
<q-input v-if="isCreate" v-model="kintonepwd" filled :type="isPwd ? 'password' : 'text'"
|
||||||
</q-card-section>
|
hint="パスワード" label="パスワード" :disable="!isCreate" lazy-rules
|
||||||
<q-card-actions align="right" class="text-primary">
|
:rules="[val => val && val.length > 0 || 'Please type something']" autocomplete="new-password">
|
||||||
<q-btn label="Save" type="submit" color="primary" @click="onSubmit" />
|
<template v-slot:append>
|
||||||
<q-btn label="Cancel" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer"
|
||||||
</q-card-actions>
|
@click="isPwd = !isPwd" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<div class="q-gutter-y-md" v-if="!isCreate">
|
||||||
|
<q-separator />
|
||||||
|
|
||||||
|
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>パスワードリセット</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-toggle v-model="resetPsw" @update:model-value="updateResetPsw" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-input v-model="kintonepwd" filled :type="isPwd ? 'password' : 'text'" hint="パスワードを入力してください"
|
||||||
|
label="パスワード" :disable="!resetPsw" lazy-rules
|
||||||
|
:rules="[val => val && val.length > 0 || 'Please type something']" autocomplete="new-password">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer"
|
||||||
|
@click="isPwd = !isPwd" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
<!-- <q-btn label="asdf"/> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="right" class="text-primary q-mb-md q-mx-sm">
|
||||||
|
<q-btn label="保存" type="submit" color="primary" />
|
||||||
|
<q-btn label="キャンセル" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-form>
|
||||||
</q-card>
|
</q-card>
|
||||||
|
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
@@ -56,7 +97,7 @@
|
|||||||
<q-dialog v-model="confirm" persistent>
|
<q-dialog v-model="confirm" persistent>
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section class="row items-center">
|
<q-card-section class="row items-center">
|
||||||
<q-avatar icon="confirm" color="primary" text-color="white" />
|
<q-icon name="warning" color="warning" size="2em" />
|
||||||
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
@@ -73,66 +114,68 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, onMounted, reactive } from 'vue';
|
import { ref, onMounted, reactive } from 'vue';
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'id' },
|
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||||
{
|
{
|
||||||
name: 'tenantid',
|
name: 'tenantid',
|
||||||
required: true,
|
required: true,
|
||||||
label: 'Tenant',
|
label: 'テナントID',
|
||||||
align: 'left',
|
|
||||||
field: row => row.tenantid,
|
field: row => row.tenantid,
|
||||||
format: val => `${val}`,
|
format: val => `${val}`,
|
||||||
|
align: 'left',
|
||||||
sortable: true
|
sortable: true
|
||||||
},
|
},
|
||||||
{ name: 'name', align: 'center', label: 'Name', field: 'name', sortable: true },
|
{ name: 'name', label: '環境名', field: 'name', align: 'left', sortable: true },
|
||||||
{ name: 'url', align: 'left', label: 'URL', field: 'url', sortable: true },
|
{ name: 'url', label: 'URL', field: 'url', align: 'left', sortable: true },
|
||||||
{ name: 'user', label: 'Account', field: 'user' },
|
{ name: 'user', label: 'ログイン名', field: 'user', align: 'left', },
|
||||||
{ name: 'password', label: 'Password', field: 'password' }
|
{ name: 'actions', label: '操作', field: 'actions' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const filter = ref('');
|
const filter = ref('');
|
||||||
const rows = ref([]);
|
const rows = ref([]);
|
||||||
const show = ref(false);
|
const show = ref(false);
|
||||||
const confirm = ref(false);
|
const confirm = ref(false);
|
||||||
const selected = ref([]);
|
const resetPsw = ref(false);
|
||||||
const tenantid = ref('');
|
|
||||||
|
const tenantid = ref(authStore.currentDomain.id);
|
||||||
const name = ref('');
|
const name = ref('');
|
||||||
const url = ref('');
|
const url = ref('');
|
||||||
const isPwd = ref(true);
|
const isPwd = ref(true);
|
||||||
const kintoneuser = ref('');
|
const kintoneuser = ref('');
|
||||||
const kintonepwd = ref('');
|
const kintonepwd = ref('');
|
||||||
|
const kintonepwdBK = ref('');
|
||||||
|
const isCreate = ref(true);
|
||||||
let editId = ref(0);
|
let editId = ref(0);
|
||||||
|
|
||||||
const getDomain = async () => {
|
const getDomain = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
const result= await api.get(`api/domains/1`);
|
const userId = authStore.userId;
|
||||||
rows.value= result.data.map((item)=>{
|
const result = await api.get(`api/domain?userId=${userId}`);
|
||||||
|
rows.value = result.data.map((item) => {
|
||||||
return { id: item.id, tenantid: item.tenantid, name: item.name, url: item.url, user: item.kintoneuser, password: item.kintonepwd }
|
return { id: item.id, tenantid: item.tenantid, name: item.name, url: item.url, user: item.kintoneuser, password: item.kintonepwd }
|
||||||
});
|
});
|
||||||
loading.value = false;
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
await getDomain();
|
await getDomain();
|
||||||
})
|
})
|
||||||
|
|
||||||
// emulate fetching data from server
|
// emulate fetching data from server
|
||||||
const addRow = () => {
|
const addRow = () => {
|
||||||
editId.value
|
// editId.value
|
||||||
|
onReset();
|
||||||
show.value = true;
|
show.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
const removeRow = () => {
|
const removeRow = (row) => {
|
||||||
//loading.value = true
|
|
||||||
confirm.value = true;
|
confirm.value = true;
|
||||||
let row = JSON.parse(JSON.stringify(selected.value[0]));
|
|
||||||
if (selected.value.length === 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
editId.value = row.id;
|
editId.value = row.id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,14 +184,11 @@ const deleteDomain = () => {
|
|||||||
getDomain();
|
getDomain();
|
||||||
})
|
})
|
||||||
editId.value = 0;
|
editId.value = 0;
|
||||||
selected.value = [];
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const editRow = () => {
|
const editRow = (row) => {
|
||||||
if (selected.value.length === 0) {
|
isCreate.value = false
|
||||||
return;
|
|
||||||
}
|
|
||||||
let row = JSON.parse(JSON.stringify(selected.value[0]));
|
|
||||||
editId.value = row.id;
|
editId.value = row.id;
|
||||||
tenantid.value = row.tenantid;
|
tenantid.value = row.tenantid;
|
||||||
name.value = row.name;
|
name.value = row.name;
|
||||||
@@ -158,6 +198,16 @@ const editRow = () => {
|
|||||||
isPwd.value = true;
|
isPwd.value = true;
|
||||||
show.value = true;
|
show.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const updateResetPsw = (value: boolean) => {
|
||||||
|
if (value === true) {
|
||||||
|
kintonepwd.value = ''
|
||||||
|
isPwd.value = true
|
||||||
|
} else {
|
||||||
|
kintonepwd.value = kintonepwdBK.value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const closeDg = () => {
|
const closeDg = () => {
|
||||||
show.value = false;
|
show.value = false;
|
||||||
onReset();
|
onReset();
|
||||||
@@ -171,7 +221,7 @@ const onSubmit = () => {
|
|||||||
'name': name.value,
|
'name': name.value,
|
||||||
'url': url.value,
|
'url': url.value,
|
||||||
'kintoneuser': kintoneuser.value,
|
'kintoneuser': kintoneuser.value,
|
||||||
'kintonepwd': kintonepwd.value
|
'kintonepwd': isCreate.value || resetPsw.value ? kintonepwd.value : ''
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
getDomain();
|
getDomain();
|
||||||
closeDg();
|
closeDg();
|
||||||
@@ -192,7 +242,7 @@ const onSubmit = () => {
|
|||||||
onReset();
|
onReset();
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
selected.value = [];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
@@ -202,5 +252,7 @@ const onReset = () => {
|
|||||||
kintonepwd.value = '';
|
kintonepwd.value = '';
|
||||||
isPwd.value = true;
|
isPwd.value = true;
|
||||||
editId.value = 0;
|
editId.value = 0;
|
||||||
|
isCreate.value = true;
|
||||||
|
resetPsw.value = false
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -1,127 +1,43 @@
|
|||||||
<!-- <template>
|
|
||||||
<div class="q-pa-md" style="max-width: 400px">
|
|
||||||
|
|
||||||
<q-form
|
|
||||||
@submit="onSubmit"
|
|
||||||
@reset="onReset"
|
|
||||||
class="q-gutter-md"
|
|
||||||
>
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
v-model="name"
|
|
||||||
label="Your name *"
|
|
||||||
hint="Kintone envirment name"
|
|
||||||
lazy-rules
|
|
||||||
:rules="[ val => val && val.length > 0 || 'Please type something']"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-input
|
|
||||||
filled type="url"
|
|
||||||
v-model="url"
|
|
||||||
label="Kintone url"
|
|
||||||
hint="Kintone domain address"
|
|
||||||
lazy-rules
|
|
||||||
:rules="[ val => val && val.length > 0,isDomain || 'Please type something']"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-input
|
|
||||||
filled
|
|
||||||
v-model="username"
|
|
||||||
label="Login user "
|
|
||||||
hint="Kintone user name"
|
|
||||||
lazy-rules
|
|
||||||
:rules="[ val => val && val.length > 0 || 'Please type something']"
|
|
||||||
/>
|
|
||||||
|
|
||||||
<q-input v-model="password" filled :type="isPwd ? 'password' : 'text'" hint="Password with toggle" label="User password">
|
|
||||||
<template v-slot:append>
|
|
||||||
<q-icon
|
|
||||||
:name="isPwd ? 'visibility_off' : 'visibility'"
|
|
||||||
class="cursor-pointer"
|
|
||||||
@click="isPwd = !isPwd"
|
|
||||||
/>
|
|
||||||
</template>
|
|
||||||
</q-input>
|
|
||||||
|
|
||||||
<q-toggle v-model="accept" label="Active Domain" />
|
|
||||||
|
|
||||||
<div>
|
|
||||||
<q-btn label="Submit" type="submit" color="primary"/>
|
|
||||||
<q-btn label="Reset" type="reset" color="primary" flat class="q-ml-sm" />
|
|
||||||
</div>
|
|
||||||
</q-form>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<script>
|
|
||||||
import { useQuasar } from 'quasar'
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
setup () {
|
|
||||||
const $q = useQuasar()
|
|
||||||
|
|
||||||
const name = ref(null)
|
|
||||||
const age = ref(null)
|
|
||||||
const accept = ref(false)
|
|
||||||
const isPwd =ref(true)
|
|
||||||
|
|
||||||
return {
|
|
||||||
name,
|
|
||||||
age,
|
|
||||||
accept,
|
|
||||||
isPwd,
|
|
||||||
isDomain(val) {
|
|
||||||
const domainPattern = /^https?\/\/:([a-zA-Z] +\.){1}([a-zA-Z]+)\.([a-zA-Z]+)$/;
|
|
||||||
return (domainPattern.test(val) || '無効なURL')
|
|
||||||
},
|
|
||||||
|
|
||||||
onSubmit () {
|
|
||||||
if (accept.value !== true) {
|
|
||||||
$q.notify({
|
|
||||||
color: 'red-5',
|
|
||||||
textColor: 'white',
|
|
||||||
icon: 'warning',
|
|
||||||
message: 'You need to accept the license and terms first'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
$q.notify({
|
|
||||||
color: 'green-4',
|
|
||||||
textColor: 'white',
|
|
||||||
icon: 'cloud_done',
|
|
||||||
message: 'Submitted'
|
|
||||||
})
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
onReset () {
|
|
||||||
name.value = null
|
|
||||||
age.value = null
|
|
||||||
accept.value = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script> -->
|
|
||||||
|
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="q-pa-md">
|
|
||||||
<q-table grid grid-header title="Domain" selection="single" :rows="rows" :columns="columns" v-model:selected="selected" row-key="name" :filter="filter" hide-header>
|
<div class="q-pa-lg">
|
||||||
|
<div class="q-gutter-sm row items-start">
|
||||||
|
<q-breadcrumbs>
|
||||||
|
<q-breadcrumbs-el icon="assignment_ind" label="ドメイン適用" />
|
||||||
|
</q-breadcrumbs>
|
||||||
|
</div>
|
||||||
|
<q-table grid grid-header title="Domain" selection="single" :rows="rows" :columns="columns" row-key="name"
|
||||||
|
:filter="userDomainTableFilter" virtual-scroll v-model:pagination="pagination">
|
||||||
<template v-slot:top>
|
<template v-slot:top>
|
||||||
<div class="q-pa-md q-gutter-sm">
|
|
||||||
<q-btn color="primary" label="追加" @click="newDomain()" dense />
|
<q-btn class="q-mx-none" color="primary" label="追加" @click="clickAddDomain()" />
|
||||||
</div>
|
|
||||||
<q-space />
|
<q-space />
|
||||||
<q-input borderless dense debounce="300" v-model="filter" placeholder="Search">
|
<div class="row q-gutter-md">
|
||||||
<template v-slot:append>
|
<q-item v-if="authStore.permissions === 'admin'" tag="label" dense @click="clickSwitchUser()">
|
||||||
<q-icon name="search" />
|
<q-item-section>
|
||||||
</template>
|
<q-item-label>適用するユーザ : </q-item-label>
|
||||||
</q-input>
|
</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
{{ currentUserName }}
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-input borderless dense filled debounce="300" v-model="userDomainTableFilter" placeholder="Search">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:header>
|
||||||
|
<div style="height: 1dvh">
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
<template v-slot:item="props">
|
<template v-slot:item="props">
|
||||||
<div class="q-pa-xs col-xs-12 col-sm-6 col-md-4">
|
<div class="q-pa-sm">
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section>
|
<q-card-section>
|
||||||
<div class="q-table__grid-item-row">
|
<div class="q-table__grid-item-row">
|
||||||
@@ -130,40 +46,73 @@ export default {
|
|||||||
</div>
|
</div>
|
||||||
<div class="q-table__grid-item-row">
|
<div class="q-table__grid-item-row">
|
||||||
<div class="q-table__grid-item-title">URL</div>
|
<div class="q-table__grid-item-title">URL</div>
|
||||||
<div class="q-table__grid-item-value">{{ props.row.url }}</div>
|
<div class="q-table__grid-item-value" style="width: 22rem;">{{ props.row.url }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="q-table__grid-item-row">
|
<div class="q-table__grid-item-row">
|
||||||
<div class="q-table__grid-item-title">Account</div>
|
<div class="q-table__grid-item-title">Account</div>
|
||||||
<div class="q-table__grid-item-value">{{ props.row.kintoneuser }}</div>
|
<div class="q-table__grid-item-value">{{ props.row.kintoneuser }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="q-table__grid-item-row">
|
|
||||||
<div class="q-table__grid-item-value">{{isActive(props.row.id) }}</div>
|
|
||||||
</div>
|
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
<q-separator />
|
<q-separator />
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
<q-btn flat @click = "activeDomain(props.row.id)">有効</q-btn>
|
<div style="width: 98%;">
|
||||||
<q-btn flat @click = "deleteConfirm(props.row)">削除</q-btn>
|
<div class="row items-center justify-between">
|
||||||
|
<div class="q-table__grid-item-value"
|
||||||
|
:class="isActive(props.row.id) ? 'text-positive' : 'text-negative'">{{
|
||||||
|
isActive(props.row.id)?'既定':'' }}</div>
|
||||||
|
<div class="col-auto">
|
||||||
|
<q-btn v-if="!isActive(props.row.id)" flat
|
||||||
|
@click="activeDomain(props.row.id)">既定にする</q-btn>
|
||||||
|
<q-btn flat @click="clickDeleteConfirm(props.row)">削除</q-btn>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</q-table>
|
</q-table>
|
||||||
|
|
||||||
<show-dialog v-model:visible="show" name="ドメイン" @close="closeDg" width="350px">
|
<show-dialog v-model:visible="showAddDomainDg" name="ドメイン" @close="addUserDomainFinished">
|
||||||
<domain-select ref="domainDg" name="ドメイン" type="multiple"></domain-select>
|
<domain-select ref="addDomainRef" name="ドメイン" type="multiple"></domain-select>
|
||||||
</show-dialog>
|
</show-dialog>
|
||||||
|
|
||||||
<q-dialog v-model="confirm" persistent>
|
<show-dialog v-model:visible="showSwitchUserDd" name="ドメイン" minWidth="35vw" @close="switchUserFinished">
|
||||||
|
<template v-slot:toolbar>
|
||||||
|
<q-input dense placeholder="検索" v-model="switchUserFilter">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
<div class="q-gutter-md">
|
||||||
|
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>他のユーザーを選択する</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-toggle v-model="useOtherUser" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
<div v-if="useOtherUser">
|
||||||
|
<user-list ref="switchUserRef" name="ドメイン" :filter="switchUserFilter" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</show-dialog>
|
||||||
|
|
||||||
|
<q-dialog v-model="showDeleteConfirm" persistent>
|
||||||
<q-card>
|
<q-card>
|
||||||
<q-card-section class="row items-center">
|
<q-card-section class="row items-center">
|
||||||
<q-avatar icon="confirm" color="primary" text-color="white" />
|
<div class="q-ma-sm q-mt-md">
|
||||||
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
<q-icon name="warning" color="warning" size="2em" />
|
||||||
|
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
||||||
|
</div>
|
||||||
</q-card-section>
|
</q-card-section>
|
||||||
|
|
||||||
<q-card-actions align="right">
|
<q-card-actions align="right">
|
||||||
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
||||||
<q-btn flat label="OK" color="primary" v-close-popup @click = "deleteDomain()"/>
|
<q-btn flat label="OK" color="primary" v-close-popup @click="deleteDomainFinished()" />
|
||||||
</q-card-actions>
|
</q-card-actions>
|
||||||
</q-card>
|
</q-card>
|
||||||
</q-dialog>
|
</q-dialog>
|
||||||
@@ -171,100 +120,117 @@ export default {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { useQuasar } from 'quasar'
|
import { ref, onMounted, computed } from 'vue'
|
||||||
import { ref, onMounted, reactive } from 'vue'
|
import { api } from 'boot/axios';
|
||||||
import ShowDialog from 'components/ShowDialog.vue';
|
|
||||||
import DomainSelect from 'components/DomainSelect.vue';
|
|
||||||
import { useAuthStore } from 'stores/useAuthStore';
|
import { useAuthStore } from 'stores/useAuthStore';
|
||||||
|
|
||||||
|
import ShowDialog from 'components/ShowDialog.vue';
|
||||||
|
import DomainSelect from 'components/DomainSelect.vue';
|
||||||
|
import UserList from 'components/UserList.vue';
|
||||||
|
|
||||||
const authStore = useAuthStore();
|
const authStore = useAuthStore();
|
||||||
import { api } from 'boot/axios';
|
const pagination = ref({ sortBy: 'id', rowsPerPage: 0 });
|
||||||
import { domain } from 'process';
|
const rows = ref([] as any[]);
|
||||||
|
|
||||||
const $q = useQuasar()
|
|
||||||
|
|
||||||
const domainDg = ref();
|
|
||||||
const selected = ref([])
|
|
||||||
|
|
||||||
const show = ref(false);
|
|
||||||
const confirm = ref(false)
|
|
||||||
|
|
||||||
let editId = ref(0);
|
|
||||||
let activedomainid = ref(0);
|
|
||||||
|
|
||||||
const columns = [
|
const columns = [
|
||||||
{ name: 'id'},
|
{ name: 'id' },
|
||||||
{name: 'name',required: true,label: 'Name',align: 'left',field: 'name',sortable: true},
|
{ name: 'name', required: true, label: 'Name', align: 'left', field: 'name', sortable: true },
|
||||||
{ name: 'url', align: 'center', label: 'Domain', field: 'url', sortable: true },
|
{ name: 'url', align: 'center', label: 'Domain', field: 'url', sortable: true },
|
||||||
{ name: 'kintoneuser', label: 'User', field: 'kintoneuser', sortable: true },
|
{ name: 'kintoneuser', label: 'User', field: 'kintoneuser', sortable: true },
|
||||||
{ name: 'kintonepwd' },
|
{ name: 'kintonepwd' },
|
||||||
{ name: 'active', field: 'active'}
|
{ name: 'active', field: 'active' }
|
||||||
]
|
];
|
||||||
|
const userDomainTableFilter = ref();
|
||||||
|
|
||||||
const rows = ref([] as any[]);
|
const currentUserName = ref('');
|
||||||
|
const useOtherUser = ref(false);
|
||||||
|
const otherUserId = ref('');
|
||||||
|
|
||||||
const isActive = (id:number) =>{
|
let editId = ref(0);
|
||||||
if(id == activedomainid.value)
|
|
||||||
return "Active";
|
|
||||||
else
|
|
||||||
return "Inactive";
|
|
||||||
}
|
|
||||||
|
|
||||||
const newDomain = () => {
|
const showAddDomainDg = ref(false);
|
||||||
|
const addDomainRef = ref();
|
||||||
|
|
||||||
|
const clickAddDomain = () => {
|
||||||
editId.value = 0;
|
editId.value = 0;
|
||||||
show.value = true;
|
showAddDomainDg.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const addUserDomainFinished = (val: string) => {
|
||||||
const activeDomain = (id:number) => {
|
if (val == 'OK') {
|
||||||
api.put(`api/activedomain/`+ id).then(() =>{
|
let dodmainids = [];
|
||||||
getDomain();
|
let domains = JSON.parse(JSON.stringify(addDomainRef.value.selected));
|
||||||
})
|
for (var key in domains) {
|
||||||
|
dodmainids.push(domains[key].id);
|
||||||
|
}
|
||||||
|
api.post(`api/domain/${useOtherUser.value ? otherUserId.value : authStore.userId}`, dodmainids)
|
||||||
|
.then(() => { getDomain(useOtherUser.value ? otherUserId.value : undefined); });
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteConfirm = (row:object) => {
|
const showDeleteConfirm = ref(false);
|
||||||
confirm.value = true;
|
|
||||||
|
const clickDeleteConfirm = (row: any) => {
|
||||||
|
showDeleteConfirm.value = true;
|
||||||
editId.value = row.id;
|
editId.value = row.id;
|
||||||
};
|
};
|
||||||
|
|
||||||
const deleteDomain = () => {
|
const deleteDomainFinished = () => {
|
||||||
api.delete(`api/domain/`+ editId.value+'/1').then(() =>{
|
api.delete(`api/domain/${editId.value}/${useOtherUser.value ? otherUserId.value : authStore.userId}`).then(() => {
|
||||||
getDomain();
|
getDomain(useOtherUser.value ? otherUserId.value : undefined);
|
||||||
})
|
})
|
||||||
editId.value = 0;
|
editId.value = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
const closeDg = (val:string) => {
|
const activeDomain = (id: number) => {
|
||||||
if (val == 'OK') {
|
api.put(`api/activedomain/${id}${useOtherUser.value ? `?userId=${otherUserId.value}` : ''}`)
|
||||||
let dodmainids =[];
|
.then(() => { getDomain(useOtherUser.value ? otherUserId.value : undefined); })
|
||||||
let domains = JSON.parse(JSON.stringify(domainDg.value.selected));
|
|
||||||
for(var key in domains)
|
|
||||||
{
|
|
||||||
dodmainids.push(domains[key].id);
|
|
||||||
}
|
|
||||||
api.post(`api/domain`, dodmainids).then(() =>{getDomain();});
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
};
|
||||||
const getDomain = async () => {
|
|
||||||
const resp = await api.get(`api/activedomain`);
|
let activeDomainId = ref(0);
|
||||||
activedomainid.value = resp.data.id;
|
|
||||||
const domainResult = await api.get(`api/domain`);
|
const isActive = computed(() => (id: number) => {
|
||||||
const domains = domainResult.data as any[];
|
return id == activeDomainId.value;
|
||||||
rows.value=domains.map((item)=>{
|
});
|
||||||
return { id:item.id,name: item.name, url: item.url, kintoneuser: item.kintoneuser, kintonepwd: item.kintonepwd}
|
|
||||||
});
|
|
||||||
|
const showSwitchUserDd = ref(false);
|
||||||
|
const switchUserRef = ref();
|
||||||
|
const switchUserFilter = ref('')
|
||||||
|
|
||||||
|
const clickSwitchUser = () => {
|
||||||
|
showSwitchUserDd.value = true;
|
||||||
|
useOtherUser.value = false;
|
||||||
|
};
|
||||||
|
|
||||||
|
const switchUserFinished = async (val: string) => {
|
||||||
|
if (val == 'OK') {
|
||||||
|
if (useOtherUser.value) {
|
||||||
|
const user = switchUserRef.value.selected[0]
|
||||||
|
currentUserName.value = user.email;
|
||||||
|
otherUserId.value = user.id
|
||||||
|
await getDomain(user.id)
|
||||||
|
} else {
|
||||||
|
currentUserName.value = authStore.userInfo.email
|
||||||
|
await getDomain();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getDomain = async (userId? : string) => {
|
||||||
|
const resp = await api.get(`api/activedomain${useOtherUser.value ? `?userId=${otherUserId.value}` : ''}`);
|
||||||
|
activeDomainId.value = resp?.data?.id;
|
||||||
|
const domainResult = userId ? await api.get(`api/domain?userId=${userId}`) : await api.get(`api/domain`);
|
||||||
|
const domains = domainResult.data as any[];
|
||||||
|
rows.value = domains.map((item) => {
|
||||||
|
return { id: item.id, name: item.name, url: item.url, kintoneuser: item.kintoneuser, kintonepwd: item.kintonepwd }
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
|
currentUserName.value = authStore.userInfo.email
|
||||||
await getDomain();
|
await getDomain();
|
||||||
})
|
})
|
||||||
|
|
||||||
const isDomain = (val) =>{
|
|
||||||
// const domainPattern = /^https\/\/:([a-zA-Z] +\.){1}([a-zA-Z]+)\.([a-zA-Z]+)$/;
|
|
||||||
// return (domainPattern.test(val) || '無効なURL')
|
|
||||||
return true;
|
|
||||||
};
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
307
frontend/src/pages/UserManagement.vue
Normal file
307
frontend/src/pages/UserManagement.vue
Normal file
@@ -0,0 +1,307 @@
|
|||||||
|
<template>
|
||||||
|
<div class="q-pa-md">
|
||||||
|
<div class="q-gutter-sm row items-start">
|
||||||
|
<q-breadcrumbs>
|
||||||
|
<q-breadcrumbs-el icon="manage_accounts" label="ユーザー管理" />
|
||||||
|
</q-breadcrumbs>
|
||||||
|
</div>
|
||||||
|
<q-table title="ユーザーリスト" :rows="rows" :columns="columns" row-key="id" :filter="filter" :loading="loading"
|
||||||
|
:pagination="pagination" >
|
||||||
|
|
||||||
|
<template v-slot:top>
|
||||||
|
<q-btn color="primary" :disable="loading" label="新規" @click="addRow" />
|
||||||
|
<q-space />
|
||||||
|
<q-input borderless dense filled debounce="300" v-model="filter" placeholder="検索">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon name="search" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body-cell-status="props">
|
||||||
|
<q-td :props="props">
|
||||||
|
<div class="row">
|
||||||
|
<div v-if="props.row.isActive">
|
||||||
|
<q-chip square color="positive" text-color="white" icon="done" label="使用可能" size="sm" />
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<q-chip square color="negative" text-color="white" icon="block" label="使用不可" size="sm" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<q-chip v-if="props.row.isSuperuser" square color="accent" text-color="white" icon="admin_panel_settings"
|
||||||
|
label="システム管理者" size="sm" />
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:header-cell-status="p">
|
||||||
|
<q-th :props="p">
|
||||||
|
<div class="row items-center">
|
||||||
|
<label class="q-mr-md">{{ p.col.label }}</label>
|
||||||
|
<q-select v-model="statusFilter" :options="options" @update:model-value="updateStatusFilter" borderless
|
||||||
|
dense options-dense style="font-size: 12px; padding-top: 1px;" />
|
||||||
|
</div>
|
||||||
|
</q-th>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-slot:body-cell-actions="p">
|
||||||
|
<q-td :props="p">
|
||||||
|
<q-btn-group flat>
|
||||||
|
<q-btn flat color="primary" padding="xs" size="1em" icon="edit_note" @click="editRow(p.row)" />
|
||||||
|
<q-btn flat color="negative" padding="xs" size="1em" icon="delete_outline" @click="removeRow(p.row)" />
|
||||||
|
</q-btn-group>
|
||||||
|
</q-td>
|
||||||
|
</template>
|
||||||
|
</q-table>
|
||||||
|
|
||||||
|
<q-dialog :model-value="show" persistent>
|
||||||
|
<q-card style="min-width: 36em">
|
||||||
|
<q-form class="q-gutter-md" @submit="onSubmit" autocomplete="off">
|
||||||
|
<q-card-section>
|
||||||
|
<div class="text-h6 q-ma-sm">K-True Account</div>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-section class="q-pt-none q-mt-none">
|
||||||
|
<div class="q-gutter-lg">
|
||||||
|
|
||||||
|
<q-input filled v-model="firstName" label="氏名 *" hint="ユーザーの氏名を入力してください" lazy-rules
|
||||||
|
:rules="[val => val && val.length > 0 || 'ユーザーの氏名を入力してください。']" />
|
||||||
|
|
||||||
|
<q-input filled v-model="lastName" label="苗字 *" hint="ユーザーの苗字を入力してください" lazy-rules
|
||||||
|
:rules="[val => val && val.length > 0 || 'ユーザーの苗字を入力してください']" />
|
||||||
|
|
||||||
|
<q-input filled type="email" v-model="email" label="電子メール *" hint="電子メール、ログインとしても使用" lazy-rules
|
||||||
|
:rules="[val => val && val.length > 0 || '電子メールを入力してください']" autocomplete="new-password" />
|
||||||
|
|
||||||
|
<q-input v-if="isCreate" v-model="pwd" filled :type="isPwd ? 'password' : 'text'" hint="パスワード"
|
||||||
|
label="パスワード" :disable="!isCreate" lazy-rules
|
||||||
|
:rules="[val => val && val.length > 0 || 'Please type something']" autocomplete="new-password">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer"
|
||||||
|
@click="isPwd = !isPwd" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
|
||||||
|
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>システム管理者</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-toggle v-model="isSuperuser" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>使用可能</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-toggle v-model="isActive" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<div class="q-gutter-y-md" v-if="!isCreate">
|
||||||
|
<q-separator />
|
||||||
|
|
||||||
|
<q-item tag="label" class="q-pl-sm q-pr-none q-py-xs">
|
||||||
|
<q-item-section>
|
||||||
|
<q-item-label>パスワードリセット</q-item-label>
|
||||||
|
</q-item-section>
|
||||||
|
<q-item-section avatar>
|
||||||
|
<q-toggle v-model="resetPsw" />
|
||||||
|
</q-item-section>
|
||||||
|
</q-item>
|
||||||
|
|
||||||
|
<q-input v-model="pwd" filled :type="isPwd ? 'password' : 'text'" hint="パスワードを入力してください" label="パスワード"
|
||||||
|
:disable="!resetPsw" lazy-rules :rules="[val => val && val.length > 0 || 'Please type something']"
|
||||||
|
autocomplete="new-password">
|
||||||
|
<template v-slot:append>
|
||||||
|
<q-icon :name="isPwd ? 'visibility_off' : 'visibility'" class="cursor-pointer"
|
||||||
|
@click="isPwd = !isPwd" />
|
||||||
|
</template>
|
||||||
|
</q-input>
|
||||||
|
<!-- <q-btn label="asdf"/> -->
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</q-card-section>
|
||||||
|
<q-card-actions align="right" class="text-primary q-mb-md q-mx-sm">
|
||||||
|
<q-btn label="保存" type="submit" color="primary" />
|
||||||
|
<q-btn label="キャンセル" type="cancel" color="primary" flat class="q-ml-sm" @click="closeDg()" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-form>
|
||||||
|
</q-card>
|
||||||
|
|
||||||
|
</q-dialog>
|
||||||
|
|
||||||
|
<q-dialog v-model="confirm" persistent>
|
||||||
|
<q-card>
|
||||||
|
<q-card-section class="row items-center">
|
||||||
|
<q-icon name="warning" color="warning" size="2em" />
|
||||||
|
<span class="q-ml-sm">削除してもよろしいですか?</span>
|
||||||
|
</q-card-section>
|
||||||
|
|
||||||
|
<q-card-actions align="right">
|
||||||
|
<q-btn flat label="Cancel" color="primary" v-close-popup />
|
||||||
|
<q-btn flat label="OK" color="primary" v-close-popup @click="deleteUser()" />
|
||||||
|
</q-card-actions>
|
||||||
|
</q-card>
|
||||||
|
</q-dialog>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { api } from 'boot/axios';
|
||||||
|
|
||||||
|
const columns = [
|
||||||
|
{ name: 'id', label: 'ID', field: 'id', align: 'left', sortable: true },
|
||||||
|
{ name: 'firstName', label: '氏名', field: 'firstName', align: 'left', sortable: true },
|
||||||
|
{ name: 'lastName', label: '苗字', field: 'lastName', align: 'left', sortable: true },
|
||||||
|
{ name: 'email', label: '電子メール', field: 'email', align: 'left', sortable: true },
|
||||||
|
{ name: 'status', label: '状況', field: 'status', align: 'left' },
|
||||||
|
{ name: 'actions', label: '操作', field: 'actions' }
|
||||||
|
];
|
||||||
|
|
||||||
|
const pagination = ref({ sortBy: 'id', descending: true, rowsPerPage: 20 });
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const filter = ref('');
|
||||||
|
const statusFilter = ref('全データ');
|
||||||
|
const rows = ref([]);
|
||||||
|
const show = ref(false);
|
||||||
|
const confirm = ref(false);
|
||||||
|
const resetPsw = ref(false);
|
||||||
|
|
||||||
|
const firstName = ref('');
|
||||||
|
const lastName = ref('');
|
||||||
|
const email = ref('');
|
||||||
|
const isSuperuser = ref(false);
|
||||||
|
const isActive = ref(true);
|
||||||
|
|
||||||
|
const isPwd = ref(true);
|
||||||
|
const pwd = ref('');
|
||||||
|
const isCreate = ref(true);
|
||||||
|
let editId = ref(0);
|
||||||
|
|
||||||
|
const getUsers = async (filter = () => true) => {
|
||||||
|
loading.value = true;
|
||||||
|
const result = await api.get(`api/v1/users`);
|
||||||
|
rows.value = result.data.map((item) => {
|
||||||
|
return { id: item.id, firstName: item.first_name, lastName: item.last_name, email: item.email, isSuperuser: item.is_superuser, isActive: item.is_active }
|
||||||
|
}).filter(filter);
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const updateStatusFilter = (status) => {
|
||||||
|
switch (status) {
|
||||||
|
case 'システム管理者のみ':
|
||||||
|
getUsers((row) => row.isSuperuser)
|
||||||
|
break;
|
||||||
|
case '使用可能':
|
||||||
|
getUsers((row) => row.isActive)
|
||||||
|
break;
|
||||||
|
case '使用不可':
|
||||||
|
getUsers((row) => !row.isActive)
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
getUsers()
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(async () => {
|
||||||
|
await getUsers();
|
||||||
|
})
|
||||||
|
|
||||||
|
const options = ['全データ', 'システム管理者のみ', '使用可能', '使用不可']
|
||||||
|
|
||||||
|
// emulate fetching data from server
|
||||||
|
const addRow = () => {
|
||||||
|
// editId.value
|
||||||
|
onReset();
|
||||||
|
show.value = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeRow = (row) => {
|
||||||
|
confirm.value = true;
|
||||||
|
editId.value = row.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
const deleteUser = () => {
|
||||||
|
api.delete(`api/v1/users/${editId.value}`).then(() => {
|
||||||
|
getUsers();
|
||||||
|
})
|
||||||
|
editId.value = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const editRow = (row) => {
|
||||||
|
isCreate.value = false
|
||||||
|
editId.value = row.id;
|
||||||
|
|
||||||
|
firstName.value = row.firstName;
|
||||||
|
lastName.value = row.lastName;
|
||||||
|
email.value = row.email;
|
||||||
|
pwd.value = row.password;
|
||||||
|
|
||||||
|
isSuperuser.value = row.isSuperuser;
|
||||||
|
isActive.value = row.isActive;
|
||||||
|
|
||||||
|
isPwd.value = true;
|
||||||
|
show.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeDg = () => {
|
||||||
|
show.value = false;
|
||||||
|
onReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSubmit = () => {
|
||||||
|
if (editId.value !== 0) {
|
||||||
|
api.put(`api/v1/users/${editId.value}`, {
|
||||||
|
'first_name': firstName.value,
|
||||||
|
'last_name': lastName.value,
|
||||||
|
'is_superuser': isSuperuser.value,
|
||||||
|
'is_active': isActive.value,
|
||||||
|
'email': email.value,
|
||||||
|
...(isCreate.value || resetPsw.value ? { password: pwd.value } : {})
|
||||||
|
}).then(() => {
|
||||||
|
getUsers();
|
||||||
|
closeDg();
|
||||||
|
onReset();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
api.post(`api/v1/users`, {
|
||||||
|
'id': 0,
|
||||||
|
'first_name': firstName.value,
|
||||||
|
'last_name': lastName.value,
|
||||||
|
'is_superuser': isSuperuser.value,
|
||||||
|
'is_active': isActive.value,
|
||||||
|
'email': email.value,
|
||||||
|
'password': pwd.value
|
||||||
|
}).then(() => {
|
||||||
|
getUsers();
|
||||||
|
closeDg();
|
||||||
|
onReset();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
const onReset = () => {
|
||||||
|
firstName.value = '';
|
||||||
|
lastName.value = '';
|
||||||
|
email.value = '';
|
||||||
|
pwd.value = '';
|
||||||
|
isActive.value = true;
|
||||||
|
isSuperuser.value = false;
|
||||||
|
isPwd.value = true;
|
||||||
|
editId.value = 0;
|
||||||
|
isCreate.value = true;
|
||||||
|
resetPsw.value = false
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -6,7 +6,7 @@ const routes: RouteRecordRaw[] = [
|
|||||||
component: () => import('pages/LoginPage.vue')
|
component: () => import('pages/LoginPage.vue')
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path:'/FlowChart',
|
path:'/FlowChart/:id',
|
||||||
component:()=>import('layouts/MainLayout.vue'),
|
component:()=>import('layouts/MainLayout.vue'),
|
||||||
children:[
|
children:[
|
||||||
{path:'',component:()=>import('pages/FlowChart.vue')}
|
{path:'',component:()=>import('pages/FlowChart.vue')}
|
||||||
@@ -25,7 +25,9 @@ const routes: RouteRecordRaw[] = [
|
|||||||
// { path: 'FlowChart', component: () => import('pages/FlowChart.vue') },
|
// { path: 'FlowChart', component: () => import('pages/FlowChart.vue') },
|
||||||
{ path: 'right', component: () => import('pages/testRight.vue') },
|
{ path: 'right', component: () => import('pages/testRight.vue') },
|
||||||
{ path: 'domain', component: () => import('pages/TenantDomain.vue') },
|
{ path: 'domain', component: () => import('pages/TenantDomain.vue') },
|
||||||
{ path: 'userdomain', component: () => import('pages/UserDomain.vue')},
|
// { path: 'userdomain', component: () => import('pages/UserDomain.vue')},
|
||||||
|
{ path: 'user', component: () => import('pages/UserManagement.vue')},
|
||||||
|
{ path: 'app', component: () => import('pages/AppManagement.vue')},
|
||||||
{ path: 'condition', component: () => import('pages/conditionPage.vue') }
|
{ path: 'condition', component: () => import('pages/conditionPage.vue') }
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,118 +1,177 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { AppInfo ,IActionFlow, IActionNode} from 'src/types/ActionTypes';
|
import { AppInfo, IActionFlow, IActionNode } from 'src/types/ActionTypes';
|
||||||
import { IKintoneEvent,KintoneEventManager } from 'src/types/KintoneEvents';
|
import { IKintoneEvent, KintoneEventManager, kintoneEvent } from 'src/types/KintoneEvents';
|
||||||
import {FlowCtrl } from '../control/flowctrl';
|
import { FlowCtrl } from '../control/flowctrl';
|
||||||
|
|
||||||
export interface FlowEditorState{
|
export interface FlowEditorState {
|
||||||
flowNames1:string;
|
flowNames1: string;
|
||||||
appInfo?:AppInfo;
|
appInfo?: AppInfo;
|
||||||
flows?:IActionFlow[];
|
flows?: IActionFlow[];
|
||||||
selectedFlow?:IActionFlow|undefined;
|
selectedFlow?: IActionFlow | undefined;
|
||||||
activeNode:IActionNode|undefined;
|
activeNode: IActionNode | undefined;
|
||||||
eventTree:KintoneEventManager;
|
eventTree: KintoneEventManager;
|
||||||
selectedEvent:IKintoneEvent|undefined;
|
selectedEvent: IKintoneEvent | undefined;
|
||||||
expandedScreen:any[];
|
expandedScreen: string[];
|
||||||
}
|
}
|
||||||
const flowCtrl=new FlowCtrl();
|
const flowCtrl = new FlowCtrl();
|
||||||
const eventTree = new KintoneEventManager();
|
const eventTree = new KintoneEventManager();
|
||||||
export const useFlowEditorStore = defineStore("flowEditor",{
|
export const useFlowEditorStore = defineStore('flowEditor', {
|
||||||
state: ():FlowEditorState => ({
|
state: (): FlowEditorState => ({
|
||||||
flowNames1: '',
|
flowNames1: '',
|
||||||
appInfo:undefined,
|
appInfo: undefined,
|
||||||
flows:[],
|
flows: [],
|
||||||
selectedFlow:undefined,
|
selectedFlow: undefined,
|
||||||
activeNode:undefined,
|
activeNode: undefined,
|
||||||
eventTree:eventTree,
|
eventTree: eventTree,
|
||||||
selectedEvent:undefined,
|
selectedEvent: undefined,
|
||||||
expandedScreen:[]
|
expandedScreen: [],
|
||||||
}),
|
}),
|
||||||
getters: {
|
getters: {
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @returns 現在編集しているフロー
|
* @returns 現在編集しているフロー
|
||||||
*/
|
*/
|
||||||
currentFlow():IActionFlow|undefined{
|
currentFlow(): IActionFlow | undefined {
|
||||||
return this.selectedFlow;
|
return this.selectedFlow;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* KintoneイベントIDから、バンドしているフローを検索する
|
* KintoneイベントIDから、バンドしているフローを検索する
|
||||||
* @param state
|
* @param state
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
findFlowByEventId(state){
|
findFlowByEventId(state) {
|
||||||
return (eventId:string)=>{
|
return (eventId: string) => {
|
||||||
return state.flows?.find((flow)=>{
|
return state.flows?.find((flow) => {
|
||||||
const root=flow.getRoot();
|
const root = flow.getRoot();
|
||||||
return root?.name===eventId
|
return root?.name === eventId;
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
}
|
},
|
||||||
|
|
||||||
|
findEventById(state) {
|
||||||
|
return (eventId: string) => {
|
||||||
|
return state.eventTree.findEventById(eventId);
|
||||||
|
};
|
||||||
|
},
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
setFlows(flows:IActionFlow[]){
|
setFlows(flows: IActionFlow[]) {
|
||||||
this.flows=flows;
|
this.flows = flows;
|
||||||
},
|
},
|
||||||
selectFlow(flow:IActionFlow){
|
selectFlow(flow: IActionFlow | undefined) {
|
||||||
this.selectedFlow=flow;
|
this.selectedFlow = flow;
|
||||||
|
if(flow!==undefined){
|
||||||
|
const eventId = flow.getRoot()?.name;
|
||||||
|
this.selectedEvent = this.eventTree.findEventById(eventId) as IKintoneEvent;
|
||||||
|
} else {
|
||||||
|
this.selectedEvent = undefined;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
setActiveNode(node:IActionNode){
|
setActiveNode(node: IActionNode) {
|
||||||
this.activeNode=node;
|
this.activeNode = node;
|
||||||
},
|
},
|
||||||
setApp(app:AppInfo){
|
setCurrentEvent(event:IKintoneEvent | undefined){
|
||||||
this.appInfo=app;
|
this.selectedEvent=event;
|
||||||
|
},
|
||||||
|
setApp(app: AppInfo) {
|
||||||
|
this.appInfo = app;
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* DBからフルーを保存する
|
* DBからフルーを保存する
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
async loadFlow(){
|
async loadFlow() {
|
||||||
if(this.appInfo===undefined) return;
|
if (this.appInfo === undefined) return;
|
||||||
const actionFlows = await flowCtrl.getFlows(this.appInfo?.appId);
|
const actionFlows = await flowCtrl.getFlows(this.appInfo?.appId);
|
||||||
//eventTreeにバンドする
|
//eventTreeにバンドする
|
||||||
this.eventTree.bindFlows(actionFlows);
|
this.eventTree.bindFlows(actionFlows);
|
||||||
if(actionFlows===undefined || actionFlows.length===0){
|
if (actionFlows === undefined || actionFlows.length === 0) {
|
||||||
this.flows=[];
|
this.setFlows([]);
|
||||||
this.selectedFlow=undefined;
|
this.selectFlow(undefined);
|
||||||
return;
|
this.expandedScreen =[];
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.setFlows(actionFlows);
|
||||||
|
if (actionFlows && actionFlows.length > 0) {
|
||||||
|
this.selectFlow(actionFlows[0]);
|
||||||
|
}
|
||||||
|
const root = actionFlows[0].getRoot();
|
||||||
|
if (root) {
|
||||||
|
this.setActiveNode(root);
|
||||||
|
}
|
||||||
|
|
||||||
|
const expandEventIds = actionFlows.map((flow) => flow.getRoot()?.name);
|
||||||
|
const expandScreens:string[]=[];
|
||||||
|
expandEventIds.forEach((eventid)=>{
|
||||||
|
const eventNode=this.eventTree.findEventById(eventid||'');
|
||||||
|
if(eventNode){
|
||||||
|
expandScreens.push(eventNode.parentId);
|
||||||
|
if(eventNode.header==='DELETABLE'){
|
||||||
|
const groupEvent = this.eventTree.findEventById(eventNode.parentId);
|
||||||
|
if(groupEvent){
|
||||||
|
expandScreens.push(groupEvent.parentId);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
this.setFlows(actionFlows);
|
});
|
||||||
if(actionFlows && actionFlows.length>0){
|
// const expandName =actionFlows[0].getRoot()?.title;
|
||||||
this.selectFlow(actionFlows[0]);
|
this.expandedScreen = expandScreens;
|
||||||
}
|
|
||||||
const expandNames = actionFlows.map(flow=>flow.getRoot()?.title);
|
|
||||||
// const expandName =actionFlows[0].getRoot()?.title;
|
|
||||||
this.expandedScreen=expandNames;
|
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* フローをDBに保存及び更新する
|
* フローをDBに保存及び更新する
|
||||||
*/
|
*/
|
||||||
async saveFlow(flow:IActionFlow){
|
async saveFlow(flow: IActionFlow):Promise<boolean> {
|
||||||
const root=flow.getRoot();
|
const root = flow.getRoot();
|
||||||
const isNew = flow.id==='';
|
const isNew = flow.id === '';
|
||||||
const jsonData={
|
const jsonData = {
|
||||||
flowid: isNew ? flow.createNewId():flow.id,
|
flowid: isNew ? flow.createNewId() : flow.id,
|
||||||
appid: this.appInfo?.appId,
|
appid: this.appInfo?.appId,
|
||||||
eventid: root?.name,
|
eventid: root?.name,
|
||||||
name: root?.subTitle,
|
name: root?.subTitle,
|
||||||
content: JSON.stringify(flow)
|
content: JSON.stringify(flow),
|
||||||
}
|
};
|
||||||
|
|
||||||
if(isNew){
|
if (isNew) {
|
||||||
return await flowCtrl.SaveFlow(jsonData);
|
return await flowCtrl.SaveFlow(jsonData);
|
||||||
}else{
|
} else {
|
||||||
return await flowCtrl.UpdateFlow(jsonData);
|
if(flow.actionNodes.length>1){
|
||||||
|
return await flowCtrl.UpdateFlow(jsonData);
|
||||||
|
}else{
|
||||||
|
const eventId = flow.getRoot()?.name||'';
|
||||||
|
const eventNode = eventTree.findEventById(eventId) as kintoneEvent;
|
||||||
|
eventNode.flowData=undefined;
|
||||||
|
return await flowCtrl.DeleteFlow(flow.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
|
||||||
|
async deleteEvent(event: IKintoneEvent) {
|
||||||
|
const store = useFlowEditorStore();
|
||||||
|
if (event.flowData) {
|
||||||
|
const flow = event.flowData;
|
||||||
|
if (flow.id !== '') {
|
||||||
|
await flowCtrl.DeleteFlow(flow.id)
|
||||||
|
if (this.flows) {
|
||||||
|
this.flows = this.flows.filter((f) => f.id !== flow.id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eventTree.deleteEvent(event, store);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
eventTree.deleteEvent(event, store);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* デプロイする
|
* デプロイする
|
||||||
*/
|
*/
|
||||||
async deploy():Promise<boolean>{
|
async deploy(): Promise<boolean> {
|
||||||
if(this.appInfo===undefined){
|
if (this.appInfo === undefined) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
return await flowCtrl.depoly(this.appInfo?.appId);
|
return await flowCtrl.depoly(this.appInfo?.appId);
|
||||||
}
|
},
|
||||||
|
},
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { store } from 'quasar/wrappers'
|
import { store } from 'quasar/wrappers'
|
||||||
import { createPinia } from 'pinia'
|
import { createPinia } from 'pinia'
|
||||||
import { Router } from 'vue-router';
|
import { Router } from 'vue-router';
|
||||||
|
import piniaPluginPersistedstate from 'pinia-plugin-persistedstate'
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* When adding new properties to stores, you should also
|
* When adding new properties to stores, you should also
|
||||||
@@ -23,10 +24,11 @@ declare module 'pinia' {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export default store((/* { ssrContext } */) => {
|
export default store((/* { ssrContext } */) => {
|
||||||
const pinia = createPinia()
|
const pinia = createPinia();
|
||||||
|
pinia.use(piniaPluginPersistedstate);
|
||||||
|
|
||||||
// You can add Pinia plugins here
|
// You can add Pinia plugins here
|
||||||
// pinia.use(SomePiniaPlugin)
|
// pinia.use(SomePiniaPlugin)
|
||||||
|
|
||||||
return pinia
|
return pinia;
|
||||||
})
|
});
|
||||||
|
|||||||
@@ -1,91 +1,122 @@
|
|||||||
import { defineStore } from 'pinia';
|
import { defineStore } from 'pinia';
|
||||||
import { api } from 'boot/axios';
|
import { api } from 'boot/axios';
|
||||||
import {router} from 'src/router';
|
import { router } from 'src/router';
|
||||||
import {IDomainInfo} from '../types/ActionTypes';
|
import { IDomainInfo } from '../types/ActionTypes';
|
||||||
|
import { jwtDecode } from 'jwt-decode';
|
||||||
|
interface UserInfo {
|
||||||
export interface IUserState{
|
firstName: string;
|
||||||
token?:string;
|
lastName: string;
|
||||||
returnUrl:string;
|
email: string;
|
||||||
currentDomain:IDomainInfo;
|
|
||||||
LeftDrawer:boolean;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const useAuthStore = defineStore({
|
export interface IUserState {
|
||||||
id: 'auth',
|
token?: string;
|
||||||
state: ():IUserState =>{
|
returnUrl: string;
|
||||||
const token=localStorage.getItem('token')||'';
|
currentDomain: IDomainInfo;
|
||||||
if(token!==''){
|
LeftDrawer: boolean;
|
||||||
api.defaults.headers["Authorization"]='Bearer ' + token;
|
userId?: string;
|
||||||
|
userInfo: UserInfo;
|
||||||
|
permissions: 'admin' | 'user';
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useAuthStore = defineStore('auth', {
|
||||||
|
state: (): IUserState => ({
|
||||||
|
token: '',
|
||||||
|
returnUrl: '',
|
||||||
|
LeftDrawer: false,
|
||||||
|
currentDomain: {} as IDomainInfo,
|
||||||
|
userId: '',
|
||||||
|
userInfo: {} as UserInfo,
|
||||||
|
permissions: 'user',
|
||||||
|
}),
|
||||||
|
getters: {
|
||||||
|
toggleLeftDrawer(): boolean {
|
||||||
|
return this.LeftDrawer;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
actions: {
|
||||||
|
setLeftMenu(value:boolean){
|
||||||
|
this.LeftDrawer=value;
|
||||||
|
},
|
||||||
|
toggleLeftMenu() {
|
||||||
|
this.LeftDrawer = !this.LeftDrawer;
|
||||||
|
},
|
||||||
|
async login(username: string, password: string) {
|
||||||
|
const params = new URLSearchParams();
|
||||||
|
params.append('username', username);
|
||||||
|
params.append('password', password);
|
||||||
|
try {
|
||||||
|
const result = await api.post(`api/token`, params);
|
||||||
|
console.info(result);
|
||||||
|
this.token = result.data.access_token;
|
||||||
|
const tokenJson = jwtDecode(result.data.access_token);
|
||||||
|
this.userId = tokenJson.sub;
|
||||||
|
this.permissions = (tokenJson as any).permissions ?? 'user';
|
||||||
|
api.defaults.headers['Authorization'] = 'Bearer ' + this.token;
|
||||||
|
this.currentDomain = await this.getCurrentDomain();
|
||||||
|
this.userInfo = await this.getUserInfo();
|
||||||
|
router.push(this.returnUrl || '/');
|
||||||
|
return true;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
async getCurrentDomain(): Promise<IDomainInfo> {
|
||||||
|
const resp = await api.get(`api/activedomain`);
|
||||||
|
const activedomain = resp?.data;
|
||||||
return {
|
return {
|
||||||
token,
|
id: activedomain?.id,
|
||||||
returnUrl: '',
|
domainName: activedomain?.name,
|
||||||
LeftDrawer:false,
|
kintoneUrl: activedomain?.url,
|
||||||
currentDomain: JSON.parse(localStorage.getItem('currentDomain')||"{}")
|
};
|
||||||
}
|
|
||||||
},
|
},
|
||||||
getters:{
|
async getUserDomains(): Promise<IDomainInfo[]> {
|
||||||
toggleLeftDrawer():boolean{
|
const resp = await api.get(`api/domain`);
|
||||||
return this.LeftDrawer;
|
const domains = resp.data as any[];
|
||||||
|
return domains.map((data) => ({
|
||||||
|
id: data.id,
|
||||||
|
domainName: data.name,
|
||||||
|
kintoneUrl: data.url,
|
||||||
|
}));
|
||||||
|
},
|
||||||
|
async getUserInfo():Promise<UserInfo>{
|
||||||
|
const resp = (await api.get(`api/v1/users/me`)).data;
|
||||||
|
return {
|
||||||
|
firstName: resp.first_name,
|
||||||
|
lastName: resp.last_name,
|
||||||
|
email: resp.email,
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
actions: {
|
logout() {
|
||||||
toggleLeftMenu(){
|
this.token = '';
|
||||||
this.LeftDrawer=!this.LeftDrawer;
|
this.currentDomain = {} as IDomainInfo; // 清空当前域
|
||||||
},
|
router.push('/login');
|
||||||
async login(username:string, password:string) {
|
},
|
||||||
const params = new URLSearchParams();
|
async setCurrentDomain(domain: IDomainInfo) {
|
||||||
params.append('username', username);
|
if (domain.id === this.currentDomain.id) {
|
||||||
params.append('password', password);
|
return;
|
||||||
try{
|
}
|
||||||
const result = await api.post(`api/token`,params);
|
await api.put(`api/activedomain/${domain.id}`);
|
||||||
console.info(result);
|
this.currentDomain = domain;
|
||||||
this.token =result.data.access_token;
|
},
|
||||||
localStorage.setItem('token', result.data.access_token);
|
},
|
||||||
api.defaults.headers["Authorization"]='Bearer ' + this.token;
|
persist: {
|
||||||
this.currentDomain=await this.getCurrentDomain();
|
afterRestore: (ctx) => {
|
||||||
localStorage.setItem('currentDomain',JSON.stringify(this.currentDomain));
|
api.defaults.headers['Authorization'] = 'Bearer ' + ctx.store.token;
|
||||||
this.router.push(this.returnUrl || '/');
|
|
||||||
return true;
|
//axios例外キャプチャー
|
||||||
}catch(e)
|
api.interceptors.response.use(
|
||||||
{
|
(response) => response,
|
||||||
console.info(e);
|
(error) => {
|
||||||
return false;
|
if (error.response && error.response.status === 401) {
|
||||||
|
// 認証エラーの場合再ログインする
|
||||||
|
console.error('(; ゚Д゚)/認証エラー(401):', error);
|
||||||
|
ctx.store.logout();
|
||||||
}
|
}
|
||||||
},
|
return Promise.reject(error);
|
||||||
async getCurrentDomain():Promise<IDomainInfo>{
|
|
||||||
const activedomain = await api.get(`api/activedomain`);
|
|
||||||
return {
|
|
||||||
id:activedomain.data.id,
|
|
||||||
domainName:activedomain.data.name,
|
|
||||||
kintoneUrl:activedomain.data.url
|
|
||||||
}
|
|
||||||
},
|
|
||||||
async getUserDomains():Promise<IDomainInfo[]>{
|
|
||||||
const resp = await api.get(`api/domain`);
|
|
||||||
const domains =resp.data as any[];
|
|
||||||
return domains.map(data=>{
|
|
||||||
return {
|
|
||||||
id:data.id,
|
|
||||||
domainName:data.name,
|
|
||||||
kintoneUrl:data.url
|
|
||||||
}
|
|
||||||
});
|
|
||||||
},
|
|
||||||
logout() {
|
|
||||||
this.token = undefined;
|
|
||||||
localStorage.removeItem('token');
|
|
||||||
localStorage.removeItem('currentDomain');
|
|
||||||
router.push('/login');
|
|
||||||
},
|
|
||||||
async setCurrentDomain(domain:IDomainInfo){
|
|
||||||
if(domain.id===this.currentDomain.id){
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
await api.put(`api/activedomain/${domain.id}`);
|
|
||||||
this.currentDomain=domain;
|
|
||||||
localStorage.setItem('currentDomain',JSON.stringify(this.currentDomain));
|
|
||||||
}
|
}
|
||||||
}
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,7 +45,16 @@ export interface IActionProperty {
|
|||||||
export interface IActionVariable{
|
export interface IActionVariable{
|
||||||
actionName:string;
|
actionName:string;
|
||||||
displayName:string;
|
displayName:string;
|
||||||
name:string;
|
name: {
|
||||||
|
name:string;
|
||||||
|
actionName:string;
|
||||||
|
displayName:string;
|
||||||
|
vars : {
|
||||||
|
vName:string;
|
||||||
|
logicalOperator:string;
|
||||||
|
field: object;
|
||||||
|
}[]
|
||||||
|
};
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* アクションタイプ定義
|
* アクションタイプ定義
|
||||||
@@ -283,6 +292,11 @@ export class ActionFlow implements IActionFlow {
|
|||||||
if (!targetNode) {
|
if (!targetNode) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
if(targetNode.isRoot){
|
||||||
|
this.actionNodes=[targetNode];
|
||||||
|
targetNode.nextNodeIds.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (targetNode.nextNodeIds.size == 0) {
|
if (targetNode.nextNodeIds.size == 0) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -303,9 +317,9 @@ export class ActionFlow implements IActionFlow {
|
|||||||
if (!targetNode) {
|
if (!targetNode) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (targetNode.nextNodeIds.size == 0) {
|
// if (targetNode.nextNodeIds.size == 0) {
|
||||||
return
|
// return
|
||||||
}
|
// }
|
||||||
for (const [, id] of targetNode.nextNodeIds) {
|
for (const [, id] of targetNode.nextNodeIds) {
|
||||||
this.removeAll(id);
|
this.removeAll(id);
|
||||||
}
|
}
|
||||||
@@ -448,6 +462,12 @@ export class ActionFlow implements IActionFlow {
|
|||||||
getPrevVarNames(prevNode:IActionNode):IActionVariable[]{
|
getPrevVarNames(prevNode:IActionNode):IActionVariable[]{
|
||||||
let varNames:IActionVariable[]=[];
|
let varNames:IActionVariable[]=[];
|
||||||
if(prevNode.varName!==undefined && prevNode.varName.modelValue){
|
if(prevNode.varName!==undefined && prevNode.varName.modelValue){
|
||||||
|
|
||||||
|
if(prevNode.varName.modelValue ==='object'){
|
||||||
|
console.log(prevNode);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
varNames.unshift({
|
varNames.unshift({
|
||||||
actionName:prevNode.name,
|
actionName:prevNode.name,
|
||||||
displayName:prevNode.varName.displayName,
|
displayName:prevNode.varName.displayName,
|
||||||
|
|||||||
20
frontend/src/types/AppTypes.ts
Normal file
20
frontend/src/types/AppTypes.ts
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
interface IUser {
|
||||||
|
first_name: string;
|
||||||
|
last_name: string;
|
||||||
|
email: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IManagedApp {
|
||||||
|
appid: string;
|
||||||
|
appname: string;
|
||||||
|
domainurl: string;
|
||||||
|
version: string;
|
||||||
|
user: IUser;
|
||||||
|
update_time: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IVersionInfo {
|
||||||
|
id: string;
|
||||||
|
name?: string;
|
||||||
|
desc?: string;
|
||||||
|
}
|
||||||
50
frontend/src/types/ComponentTypes.ts
Normal file
50
frontend/src/types/ComponentTypes.ts
Normal file
@@ -0,0 +1,50 @@
|
|||||||
|
|
||||||
|
export interface IApp {
|
||||||
|
id: string,
|
||||||
|
name: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IField {
|
||||||
|
label?:string;
|
||||||
|
code:string;
|
||||||
|
type?:string;
|
||||||
|
required?:boolean;
|
||||||
|
options?:string;
|
||||||
|
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 選択されたフィールド
|
||||||
|
*/
|
||||||
|
export interface ISelectedField extends IField{
|
||||||
|
objectType:'Field'|'RefField';
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IAppFields {
|
||||||
|
app?: IApp,
|
||||||
|
name?:string;
|
||||||
|
fields: IField[]
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 条件式の入力ボタンの属性定義
|
||||||
|
*/
|
||||||
|
export interface IButtonConfig{
|
||||||
|
label: string;
|
||||||
|
color: string;
|
||||||
|
type: 'FieldAdd' | 'VariableAdd' | 'FunctionAdd';
|
||||||
|
};
|
||||||
|
/**
|
||||||
|
* 条件入力項目の属性
|
||||||
|
*/
|
||||||
|
export interface IDynamicInputConfig{
|
||||||
|
canInput: boolean;
|
||||||
|
buttonsConfig: IButtonConfig[];
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* 条件式入力項目の属性
|
||||||
|
*/
|
||||||
|
export interface ICoditionConfig{
|
||||||
|
left:IDynamicInputConfig,
|
||||||
|
right:IDynamicInputConfig
|
||||||
|
}
|
||||||
@@ -74,6 +74,11 @@ export class GroupNode implements INode {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type OperatorListItem = {
|
||||||
|
label: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
// 条件式ノード
|
// 条件式ノード
|
||||||
export class ConditionNode implements INode {
|
export class ConditionNode implements INode {
|
||||||
index: number;
|
index: number;
|
||||||
@@ -83,13 +88,13 @@ export class ConditionNode implements INode {
|
|||||||
return this.parent.logicalOperator;
|
return this.parent.logicalOperator;
|
||||||
};
|
};
|
||||||
object: any; // 比較元
|
object: any; // 比較元
|
||||||
operator: Operator; // 比較子
|
operator: Operator | OperatorListItem; // 比較子
|
||||||
value: any;
|
value: any;
|
||||||
get header():string{
|
get header():string{
|
||||||
return 'generic';
|
return 'generic';
|
||||||
}
|
}
|
||||||
|
|
||||||
constructor(object: any, operator: Operator, value: any, parent: GroupNode) {
|
constructor(object: any, operator: Operator | OperatorListItem, value: any, parent: GroupNode) {
|
||||||
this.index=0;
|
this.index=0;
|
||||||
this.type = NodeType.Condition;
|
this.type = NodeType.Condition;
|
||||||
this.object = object;
|
this.object = object;
|
||||||
@@ -113,10 +118,12 @@ export class ConditionNode implements INode {
|
|||||||
export class ConditionTree {
|
export class ConditionTree {
|
||||||
root: GroupNode;
|
root: GroupNode;
|
||||||
maxIndex:number;
|
maxIndex:number;
|
||||||
|
queryString:string;
|
||||||
|
|
||||||
constructor() {
|
constructor() {
|
||||||
this.maxIndex=0;
|
this.maxIndex=0;
|
||||||
this.root = new GroupNode(LogicalOperator.AND, null);
|
this.root = new GroupNode(LogicalOperator.AND, null);
|
||||||
|
this.queryString='';
|
||||||
}
|
}
|
||||||
|
|
||||||
// ノード追加
|
// ノード追加
|
||||||
@@ -193,17 +200,55 @@ export class ConditionTree {
|
|||||||
return conditionString;
|
return conditionString;
|
||||||
} else {
|
} else {
|
||||||
const condNode=node as ConditionNode;
|
const condNode=node as ConditionNode;
|
||||||
if (condNode.object && condNode.operator ) {
|
if (condNode.object && condNode.object.sharedText && condNode.operator ) {
|
||||||
let value=condNode.value;
|
// let value=condNode.value;
|
||||||
if(value && typeof value ==='object' && ('label' in value)){
|
// if(value && typeof value ==='object' && ('label' in value)){
|
||||||
value =condNode.value.label;
|
// value =condNode.value.label;
|
||||||
}
|
// }
|
||||||
return `${condNode.object.name} ${condNode.operator} '${value}'`;
|
const rightVal = condNode.value.sharedText || '""';
|
||||||
|
return `${condNode.object.sharedText} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} ${rightVal}`;
|
||||||
|
// return `${typeof condNode.object.name === 'object' ? condNode.object.name.name : condNode.object.name} ${typeof condNode.operator === 'object' ? condNode.operator.label : condNode.operator} '${value}'`;
|
||||||
} else {
|
} else {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buildConditionQueryString(node:INode){
|
||||||
|
if (node.type !== NodeType.Condition) {
|
||||||
|
let conditionString = '';
|
||||||
|
if(node.type !== NodeType.Root){
|
||||||
|
conditionString = '(';
|
||||||
|
}
|
||||||
|
|
||||||
|
const groupNode = node as GroupNode;
|
||||||
|
for (let i = 0; i < groupNode.children.length; i++) {
|
||||||
|
const childConditionString = this.buildConditionQueryString(groupNode.children[i]);
|
||||||
|
if (childConditionString !== '') {
|
||||||
|
conditionString += childConditionString;
|
||||||
|
if (i < groupNode.children.length - 1) {
|
||||||
|
conditionString += ` ${groupNode.logicalOperator.toLowerCase()} `;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(node.type !== NodeType.Root){
|
||||||
|
conditionString += ')';
|
||||||
|
}
|
||||||
|
return conditionString;
|
||||||
|
} else {
|
||||||
|
const condNode=node as ConditionNode;
|
||||||
|
if (condNode.object && condNode.operator ) {
|
||||||
|
if (!condNode.object.code || !condNode.value.sharedText){
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
return `${condNode.object.code} ${typeof condNode.operator === 'object' ? condNode.operator.value : condNode.operator} "${condNode.value.sharedText}"`;
|
||||||
|
} else {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param node ノード移動
|
* @param node ノード移動
|
||||||
@@ -325,7 +370,7 @@ export class ConditionTree {
|
|||||||
}
|
}
|
||||||
|
|
||||||
toJson():string{
|
toJson():string{
|
||||||
return JSON.stringify(this.root, (key, value) => {
|
return JSON.stringify({queryString :this.queryString, ...this.root}, (key, value) => {
|
||||||
if (key === 'parent') {
|
if (key === 'parent') {
|
||||||
return value ? value.type : null;
|
return value ? value.type : null;
|
||||||
}
|
}
|
||||||
@@ -333,4 +378,7 @@ export class ConditionTree {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
setQuery(queryString:string){
|
||||||
|
this.queryString=queryString;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
import {IActionFlow} from './ActionTypes';
|
import { useFlowEditorStore } from 'src/stores/flowEditor';
|
||||||
|
import { IActionFlow } from './ActionTypes';
|
||||||
export interface IKintoneEventNode {
|
export interface IKintoneEventNode {
|
||||||
label: string;
|
label: string;
|
||||||
header:string;
|
header: string;
|
||||||
eventId:string;
|
eventId: string;
|
||||||
parentId:string;
|
parentId: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IKintoneEvent extends IKintoneEventNode {
|
export interface IKintoneEvent extends IKintoneEventNode {
|
||||||
@@ -15,60 +16,65 @@ export interface IKintoneEventGroup extends IKintoneEventNode {
|
|||||||
events: IKintoneEventNode[];
|
events: IKintoneEventNode[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class kintoneEvent implements IKintoneEvent {
|
||||||
export class kintoneEvent implements IKintoneEvent{
|
|
||||||
eventId: string;
|
eventId: string;
|
||||||
parentId:string;
|
parentId: string;
|
||||||
get hasFlow(): boolean{
|
get hasFlow(): boolean {
|
||||||
return this.flowData!==undefined && this.flowData.actionNodes.length>1
|
return this.flowData !== undefined && this.flowData.actionNodes.length > 1;
|
||||||
};
|
}
|
||||||
flowData?: IActionFlow | undefined;
|
flowData?: IActionFlow | undefined;
|
||||||
label: string;
|
label: string;
|
||||||
get header():string{
|
header :string;
|
||||||
return "EVENT";
|
constructor(label: string, eventId: string, parentId: string,header?:string) {
|
||||||
}
|
this.eventId = eventId;
|
||||||
constructor(label:string,eventId:string,parentId:string){
|
this.label = label;
|
||||||
this.eventId=eventId;
|
this.parentId = parentId;
|
||||||
this.label=label;
|
this.header=header?header:'EVENT';
|
||||||
this.parentId=parentId;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class kintoneEventGroup implements IKintoneEventGroup{
|
export class kintoneEventGroup implements IKintoneEventGroup {
|
||||||
eventId: string;
|
eventId: string;
|
||||||
parentId:string;
|
parentId: string;
|
||||||
label: string;
|
label: string;
|
||||||
events: IKintoneEventNode[];
|
events: IKintoneEventNode[];
|
||||||
get header():string{
|
get header(): string {
|
||||||
return "EVENTGROUP";
|
return 'EVENTGROUP';
|
||||||
}
|
}
|
||||||
constructor(eventId:string,label:string,events:IKintoneEventNode[],parentId:string){
|
constructor(
|
||||||
this.eventId=eventId;
|
eventId: string,
|
||||||
this.label=label;
|
label: string,
|
||||||
this.events=events;
|
events: IKintoneEventNode[],
|
||||||
this.parentId=parentId;
|
parentId: string
|
||||||
|
) {
|
||||||
|
this.eventId = eventId;
|
||||||
|
this.label = label;
|
||||||
|
this.events = events;
|
||||||
|
this.parentId = parentId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class kintoneEventForChange implements IKintoneEventGroup {
|
||||||
export class kintoneEventForChange implements IKintoneEventGroup{
|
|
||||||
eventId: string;
|
eventId: string;
|
||||||
parentId:string;
|
parentId: string;
|
||||||
label: string;
|
label: string;
|
||||||
events: IKintoneEventNode[];
|
events: IKintoneEventNode[];
|
||||||
get header():string{
|
get header(): string {
|
||||||
return "CHANGE";
|
return 'CHANGE';
|
||||||
}
|
}
|
||||||
constructor(eventId:string,label:string,events:IKintoneEventNode[],parentId:string){
|
constructor(
|
||||||
this.eventId=eventId;
|
eventId: string,
|
||||||
this.label=label;
|
label: string,
|
||||||
this.events=events;
|
events: IKintoneEventNode[],
|
||||||
this.parentId=parentId;
|
parentId: string
|
||||||
|
) {
|
||||||
|
this.eventId = eventId;
|
||||||
|
this.label = label;
|
||||||
|
this.events = events;
|
||||||
|
this.parentId = parentId;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
export class KintoneEventManager {
|
export class KintoneEventManager {
|
||||||
public screens: IKintoneEventGroup[];
|
public screens: IKintoneEventGroup[];
|
||||||
|
|
||||||
@@ -76,27 +82,24 @@ export class KintoneEventManager {
|
|||||||
this.screens = this.getKintoneEvents();
|
this.screens = this.getKintoneEvents();
|
||||||
}
|
}
|
||||||
|
|
||||||
public bindFlows(flows:IActionFlow[]){
|
public bindFlows(flows: IActionFlow[]) {
|
||||||
this.screens=this.getKintoneEvents();
|
this.screens = this.getKintoneEvents();
|
||||||
for (const flow of flows){
|
for (const flow of flows) {
|
||||||
const eventId =flow.getRoot()?.name;
|
const eventId = flow.getRoot()?.name;
|
||||||
if(eventId!==undefined){
|
if (eventId !== undefined) {
|
||||||
const eventNode = this.findEventById(eventId);
|
const eventNode = this.findEventById(eventId);
|
||||||
if(eventNode!==null && eventNode.header==="EVENT"){
|
if (eventNode !== null && eventNode.header === 'EVENT') {
|
||||||
const event =eventNode as kintoneEvent;
|
const event = eventNode as kintoneEvent;
|
||||||
event.flowData=flow;
|
event.flowData = flow;
|
||||||
}else{
|
} else {
|
||||||
//EventGroupのIDを取得
|
//EventGroupのIDを取得
|
||||||
const lastIndex = eventId.lastIndexOf(".");
|
const lastIndex = eventId.lastIndexOf('.');
|
||||||
const groupId=eventId.substring(0,lastIndex);
|
const groupId = eventId.substring(0, lastIndex);
|
||||||
const eventNode = this.findEventById(groupId);
|
const eventNode = this.findEventById(groupId);
|
||||||
if(eventNode && (eventNode.header==="EVENTGROUP" || eventNode.header==="CHANGE")){
|
if (eventNode && (eventNode.header === 'EVENTGROUP' || eventNode.header === 'CHANGE')) {
|
||||||
const groupEvent=eventNode as kintoneEventGroup;
|
const groupEvent = eventNode as kintoneEventGroup;
|
||||||
const newEvent =new kintoneEvent(
|
const label=flow.getRoot()?.subTitle || '';
|
||||||
flow.getRoot()?.subTitle || "",
|
const newEvent = new kintoneEvent(label,eventId,groupId,'DELETABLE');
|
||||||
eventId,
|
|
||||||
groupEvent.parentId
|
|
||||||
);
|
|
||||||
newEvent.flowData=flow;
|
newEvent.flowData=flow;
|
||||||
groupEvent.events.push(newEvent);
|
groupEvent.events.push(newEvent);
|
||||||
}
|
}
|
||||||
@@ -106,61 +109,218 @@ export class KintoneEventManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public findEventById(eventId: string): IKintoneEventNode | null {
|
public findEventById(eventId: string): IKintoneEventNode | null {
|
||||||
const screen=this.findScreen(eventId);
|
const screen = this.findScreen(eventId);
|
||||||
if(screen) {return screen;}
|
if (screen) {
|
||||||
|
return screen;
|
||||||
|
}
|
||||||
for (const screen of this.screens) {
|
for (const screen of this.screens) {
|
||||||
for (const event of screen.events) {
|
for (const event of screen.events) {
|
||||||
if (event.eventId === eventId) {
|
if (event.eventId === eventId) {
|
||||||
return event;
|
return event;
|
||||||
}
|
}
|
||||||
if(event.header==="EVENTGROUP"||event.header==="CHANGE"){
|
if (event.header === 'EVENTGROUP' || event.header === 'CHANGE') {
|
||||||
const eventGroup = event as IKintoneEventGroup;
|
const eventGroup = event as IKintoneEventGroup;
|
||||||
const targetEvent = eventGroup.events.find((ev)=>{
|
const targetEvent = eventGroup.events.find((ev) => {
|
||||||
return ev.eventId===eventId;
|
return ev.eventId === eventId;
|
||||||
})
|
});
|
||||||
if(targetEvent){
|
if (targetEvent) {
|
||||||
return targetEvent;
|
return targetEvent;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public findScreen(eventId:string):IKintoneEventGroup|undefined{
|
public findAllFlows():IActionFlow[]{
|
||||||
return this.screens.find(screen=>screen.eventId==eventId);
|
const flows:IActionFlow[]=[];
|
||||||
|
for (const screen of this.screens) {
|
||||||
|
for (const event of screen.events) {
|
||||||
|
if (event.header === "EVENT") {
|
||||||
|
const eventNode = event as IKintoneEvent;
|
||||||
|
if(eventNode.flowData!==undefined){
|
||||||
|
flows.push(eventNode.flowData);
|
||||||
|
}
|
||||||
|
}else if (event.header === 'EVENTGROUP' || event.header === 'CHANGE') {
|
||||||
|
const eventGroup = event as IKintoneEventGroup;
|
||||||
|
eventGroup.events.forEach((ev) => {
|
||||||
|
if (ev.header === "EVENT" || ev.header === "DELETABLE") {
|
||||||
|
const eventNode = ev as IKintoneEvent;
|
||||||
|
if(eventNode.flowData!==undefined){
|
||||||
|
flows.push(eventNode.flowData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return flows;
|
||||||
}
|
}
|
||||||
|
|
||||||
public getKintoneEvents():IKintoneEventGroup[]{
|
public findScreen(eventId: string): IKintoneEventGroup | undefined {
|
||||||
|
return this.screens.find((screen) => screen.eventId == eventId);
|
||||||
|
}
|
||||||
|
|
||||||
|
public deleteEvent(
|
||||||
|
event: kintoneEvent,
|
||||||
|
store: ReturnType<typeof useFlowEditorStore>
|
||||||
|
) {
|
||||||
|
if (event.header !== 'DELETABLE') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parent = store.findEventById(event.parentId);
|
||||||
|
if (parent?.header !== 'CHANGE' && parent?.header !== 'EVENTGROUP') {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const realParent = parent as kintoneEventForChange;
|
||||||
|
|
||||||
|
const index = realParent.events.findIndex(
|
||||||
|
(e) => e.eventId === event.eventId
|
||||||
|
);
|
||||||
|
|
||||||
|
if (index !== -1) {
|
||||||
|
realParent.events.splice(index, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public getKintoneEvents(): IKintoneEventGroup[] {
|
||||||
return [
|
return [
|
||||||
new kintoneEventGroup("app.record.create","レコード追加画面",[
|
new kintoneEventGroup(
|
||||||
new kintoneEvent('レコード追加画面を表示した後','app.record.create.show',"app.record.create"),
|
'app.record.create',
|
||||||
new kintoneEvent('保存をクリックしたとき','app.record.create.submit',"app.record.create"),
|
'レコード追加画面',
|
||||||
new kintoneEvent('保存が成功したとき','app.record.create.submit.success',"app.record.create"),
|
[
|
||||||
new kintoneEventForChange('app.record.create.change','フィールドの値を変更したとき',[],"app.record.create"),
|
new kintoneEvent(
|
||||||
new kintoneEventGroup('app.record.create.show.customButtonClick','ボタンをクリックした時',[],"app.record.create")
|
'レコード追加画面を表示した後',
|
||||||
],""),
|
'app.record.create.show',
|
||||||
new kintoneEventGroup("app.record.detail","レコード詳細画面",[
|
'app.record.create'
|
||||||
new kintoneEvent('レコード詳細画面を表示した後','app.record.detail.show',"app.record.detail"),
|
),
|
||||||
new kintoneEvent('レコードを削除するとき','app.record.detail.delete.submit',"app.record.detail"),
|
new kintoneEvent(
|
||||||
new kintoneEvent('プロセス管理のアクションを実行したとき','app.record.detail.process.proceed',"app.record.detail"),
|
'保存をクリックしたとき',
|
||||||
new kintoneEventGroup('app.record.detail.show.customButtonClick','ボタンをクリックした時',[],"app.record.detail"),
|
'app.record.create.submit',
|
||||||
],""),
|
'app.record.create'
|
||||||
new kintoneEventGroup("app.record.edit","レコード編集画面",[
|
),
|
||||||
new kintoneEvent('レコード編集画面を表示した後','app.record.edit.show',"app.record.edit"),
|
new kintoneEvent(
|
||||||
new kintoneEvent('保存をクリックしたとき','app.record.edit.submit',"app.record.edit"),
|
'保存が成功したとき',
|
||||||
new kintoneEvent('保存が成功したとき','app.record.edit.submit.success',"app.record.edit"),
|
'app.record.create.submit.success',
|
||||||
new kintoneEventForChange('app.record.edit.change','フィールドの値を変更したとき',[],"app.record.edit"),
|
'app.record.create'
|
||||||
new kintoneEventGroup('app.record.edit.show.customButtonClick','ボタンをクリックした時',[],"app.record.edit"),
|
),
|
||||||
],""),
|
new kintoneEventForChange(
|
||||||
new kintoneEventGroup("app.record.index","レコード一覧画面",[
|
'app.record.create.change',
|
||||||
new kintoneEvent('一覧画面を表示した後', 'app.record.index.show',"app.record.index"),
|
'フィールドの値を変更したとき',
|
||||||
new kintoneEvent('インライン編集を開始したとき','app.record.index.edit.show',"app.record.index"),
|
[],
|
||||||
new kintoneEvent('インライン編集の【保存】をクリックしたとき','app.record.index.edit.submit',"app.record.index"),
|
'app.record.create'
|
||||||
new kintoneEvent('インライン編集の保存が成功したとき', 'app.record.index.edit.submit.success',"app.record.index"),
|
),
|
||||||
new kintoneEventForChange('app.record.index.edit.change','インライン編集のフィールド値を変更したとき' ,[],"app.record.index"),
|
new kintoneEventGroup(
|
||||||
new kintoneEventGroup('app.record.detail.show.customButtonClick','ボタンをクリックした時',[],"app.record.index"),
|
'app.record.create.show.customButtonClick',
|
||||||
],"")
|
'ボタンをクリックしたとき',
|
||||||
|
[],
|
||||||
|
'app.record.create'
|
||||||
|
),
|
||||||
|
],
|
||||||
|
''
|
||||||
|
),
|
||||||
|
new kintoneEventGroup(
|
||||||
|
'app.record.detail',
|
||||||
|
'レコード詳細画面',
|
||||||
|
[
|
||||||
|
new kintoneEvent(
|
||||||
|
'レコード詳細画面を表示した後',
|
||||||
|
'app.record.detail.show',
|
||||||
|
'app.record.detail'
|
||||||
|
),
|
||||||
|
new kintoneEvent(
|
||||||
|
'レコードを削除するとき',
|
||||||
|
'app.record.detail.delete.submit',
|
||||||
|
'app.record.detail'
|
||||||
|
),
|
||||||
|
new kintoneEvent(
|
||||||
|
'プロセス管理のアクションを実行したとき',
|
||||||
|
'app.record.detail.process.proceed',
|
||||||
|
'app.record.detail'
|
||||||
|
),
|
||||||
|
new kintoneEventGroup(
|
||||||
|
'app.record.detail.show.customButtonClick',
|
||||||
|
'ボタンをクリックしたとき',
|
||||||
|
[],
|
||||||
|
'app.record.detail'
|
||||||
|
),
|
||||||
|
],
|
||||||
|
''
|
||||||
|
),
|
||||||
|
new kintoneEventGroup(
|
||||||
|
'app.record.edit',
|
||||||
|
'レコード編集画面',
|
||||||
|
[
|
||||||
|
new kintoneEvent(
|
||||||
|
'レコード編集画面を表示した後',
|
||||||
|
'app.record.edit.show',
|
||||||
|
'app.record.edit'
|
||||||
|
),
|
||||||
|
new kintoneEvent(
|
||||||
|
'保存をクリックしたとき',
|
||||||
|
'app.record.edit.submit',
|
||||||
|
'app.record.edit'
|
||||||
|
),
|
||||||
|
new kintoneEvent(
|
||||||
|
'保存が成功したとき',
|
||||||
|
'app.record.edit.submit.success',
|
||||||
|
'app.record.edit'
|
||||||
|
),
|
||||||
|
new kintoneEventForChange(
|
||||||
|
'app.record.edit.change',
|
||||||
|
'フィールドの値を変更したとき',
|
||||||
|
[],
|
||||||
|
'app.record.edit'
|
||||||
|
),
|
||||||
|
new kintoneEventGroup(
|
||||||
|
'app.record.edit.show.customButtonClick',
|
||||||
|
'ボタンをクリックしたとき',
|
||||||
|
[],
|
||||||
|
'app.record.edit'
|
||||||
|
),
|
||||||
|
],
|
||||||
|
''
|
||||||
|
),
|
||||||
|
new kintoneEventGroup(
|
||||||
|
'app.record.index',
|
||||||
|
'レコード一覧画面',
|
||||||
|
[
|
||||||
|
new kintoneEvent(
|
||||||
|
'一覧画面を表示した後',
|
||||||
|
'app.record.index.show',
|
||||||
|
'app.record.index'
|
||||||
|
),
|
||||||
|
new kintoneEvent(
|
||||||
|
'インライン編集を開始したとき',
|
||||||
|
'app.record.index.edit.show',
|
||||||
|
'app.record.index'
|
||||||
|
),
|
||||||
|
new kintoneEvent(
|
||||||
|
'インライン編集の保存をクリックしたとき',
|
||||||
|
'app.record.index.edit.submit',
|
||||||
|
'app.record.index'
|
||||||
|
),
|
||||||
|
new kintoneEvent(
|
||||||
|
'インライン編集の保存が成功したとき',
|
||||||
|
'app.record.index.edit.submit.success',
|
||||||
|
'app.record.index'
|
||||||
|
),
|
||||||
|
// new kintoneEventForChange(
|
||||||
|
// 'app.record.index.edit.change',
|
||||||
|
// 'インライン編集のフィールド値を変更したとき',
|
||||||
|
// [],
|
||||||
|
// 'app.record.index'
|
||||||
|
// ),
|
||||||
|
new kintoneEventGroup(
|
||||||
|
'app.record.index.show.customButtonClick',
|
||||||
|
'ボタンをクリックしたとき',
|
||||||
|
[],
|
||||||
|
'app.record.index'
|
||||||
|
),
|
||||||
|
],
|
||||||
|
''
|
||||||
|
),
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -283,6 +283,11 @@
|
|||||||
resolved "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.3.tgz"
|
resolved "https://registry.npmjs.org/@types/uuid/-/uuid-9.0.3.tgz"
|
||||||
integrity sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==
|
integrity sha512-taHQQH/3ZyI3zP8M/puluDEIEvtQHVYcC6y3N8ijFtAd28+Ey/G4sg1u2gB01S8MwybLOKAp9/yCMu/uR5l3Ug==
|
||||||
|
|
||||||
|
"@types/web-bluetooth@^0.0.20":
|
||||||
|
version "0.0.20"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597"
|
||||||
|
integrity sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow==
|
||||||
|
|
||||||
"@typescript-eslint/eslint-plugin@^5.10.0":
|
"@typescript-eslint/eslint-plugin@^5.10.0":
|
||||||
version "5.61.0"
|
version "5.61.0"
|
||||||
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz"
|
resolved "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.61.0.tgz"
|
||||||
@@ -419,6 +424,11 @@
|
|||||||
resolved "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz"
|
resolved "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz"
|
||||||
integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
|
integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
|
||||||
|
|
||||||
|
"@vue/devtools-api@^6.6.3":
|
||||||
|
version "6.6.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vue/devtools-api/-/devtools-api-6.6.3.tgz#b23a588154cba8986bba82b6e1d0248bde3fd1a0"
|
||||||
|
integrity sha512-0MiMsFma/HqA6g3KLKn+AGpL1kgKhFWszC9U29NfpWK5LE7bjeXxySWJrOJ77hBz+TBrBQ7o4QJqbPbqbs8rJw==
|
||||||
|
|
||||||
"@vue/reactivity-transform@3.3.4":
|
"@vue/reactivity-transform@3.3.4":
|
||||||
version "3.3.4"
|
version "3.3.4"
|
||||||
resolved "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz"
|
resolved "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz"
|
||||||
@@ -467,6 +477,28 @@
|
|||||||
resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz"
|
resolved "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz"
|
||||||
integrity sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==
|
integrity sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==
|
||||||
|
|
||||||
|
"@vueuse/core@^10.9.0":
|
||||||
|
version "10.11.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vueuse/core/-/core-10.11.1.tgz#15d2c0b6448d2212235b23a7ba29c27173e0c2c6"
|
||||||
|
integrity sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww==
|
||||||
|
dependencies:
|
||||||
|
"@types/web-bluetooth" "^0.0.20"
|
||||||
|
"@vueuse/metadata" "10.11.1"
|
||||||
|
"@vueuse/shared" "10.11.1"
|
||||||
|
vue-demi ">=0.14.8"
|
||||||
|
|
||||||
|
"@vueuse/metadata@10.11.1":
|
||||||
|
version "10.11.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vueuse/metadata/-/metadata-10.11.1.tgz#209db7bb5915aa172a87510b6de2ca01cadbd2a7"
|
||||||
|
integrity sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw==
|
||||||
|
|
||||||
|
"@vueuse/shared@10.11.1":
|
||||||
|
version "10.11.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/@vueuse/shared/-/shared-10.11.1.tgz#62b84e3118ae6e1f3ff38f4fbe71b0c5d0f10938"
|
||||||
|
integrity sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA==
|
||||||
|
dependencies:
|
||||||
|
vue-demi ">=0.14.8"
|
||||||
|
|
||||||
accepts@~1.3.5, accepts@~1.3.8:
|
accepts@~1.3.5, accepts@~1.3.8:
|
||||||
version "1.3.8"
|
version "1.3.8"
|
||||||
resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz"
|
resolved "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz"
|
||||||
@@ -1830,6 +1862,11 @@ jsonfile@^6.0.1:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
graceful-fs "^4.1.6"
|
graceful-fs "^4.1.6"
|
||||||
|
|
||||||
|
jwt-decode@^4.0.0:
|
||||||
|
version "4.0.0"
|
||||||
|
resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b"
|
||||||
|
integrity sha512-+KJGIyHgkGuIq3IEBNftfhW/LfWhXUIY6OmyVWjliu5KH1y0fw7VQ8YndE2O4qZdMSd9SqbnC8GOcZEy0Om7sA==
|
||||||
|
|
||||||
kind-of@^6.0.2:
|
kind-of@^6.0.2:
|
||||||
version "6.0.3"
|
version "6.0.3"
|
||||||
resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz"
|
resolved "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz"
|
||||||
@@ -2212,13 +2249,18 @@ picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.2, picomatch@^2.3.1:
|
|||||||
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
resolved "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz"
|
||||||
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
|
||||||
|
|
||||||
pinia@^2.1.6:
|
pinia-plugin-persistedstate@^3.2.1:
|
||||||
version "2.1.6"
|
version "3.2.1"
|
||||||
resolved "https://registry.npmjs.org/pinia/-/pinia-2.1.6.tgz"
|
resolved "https://registry.yarnpkg.com/pinia-plugin-persistedstate/-/pinia-plugin-persistedstate-3.2.1.tgz#66780602aecd6c7b152dd7e3ddc249a1f7a13fe5"
|
||||||
integrity sha512-bIU6QuE5qZviMmct5XwCesXelb5VavdOWKWaB17ggk++NUwQWWbP5YnsONTk3b752QkW9sACiR81rorpeOMSvQ==
|
integrity sha512-MK++8LRUsGF7r45PjBFES82ISnPzyO6IZx3CH5vyPseFLZCk1g2kgx6l/nW8pEBKxxd4do0P6bJw+mUSZIEZUQ==
|
||||||
|
|
||||||
|
pinia@^2.1.7:
|
||||||
|
version "2.2.1"
|
||||||
|
resolved "https://registry.yarnpkg.com/pinia/-/pinia-2.2.1.tgz#7cf860f6a23981c23e58605cee45496ce46d15d1"
|
||||||
|
integrity sha512-ltEU3xwiz5ojVMizdP93AHi84Rtfz0+yKd8ud75hr9LVyWX2alxp7vLbY1kFm7MXFmHHr/9B08Xf8Jj6IHTEiQ==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@vue/devtools-api" "^6.5.0"
|
"@vue/devtools-api" "^6.6.3"
|
||||||
vue-demi ">=0.14.5"
|
vue-demi "^0.14.10"
|
||||||
|
|
||||||
postcss-selector-parser@^6.0.9:
|
postcss-selector-parser@^6.0.9:
|
||||||
version "6.0.13"
|
version "6.0.13"
|
||||||
@@ -2792,10 +2834,10 @@ vite@^2.9.13:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
vue-demi@>=0.14.5:
|
vue-demi@>=0.14.8, vue-demi@^0.14.10:
|
||||||
version "0.14.6"
|
version "0.14.10"
|
||||||
resolved "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.6.tgz"
|
resolved "https://registry.yarnpkg.com/vue-demi/-/vue-demi-0.14.10.tgz#afc78de3d6f9e11bf78c55e8510ee12814522f04"
|
||||||
integrity sha512-8QA7wrYSHKaYgUxDA5ZC24w+eHm3sYCbp0EzcDwKqN3p6HqtTCGR/GVsPyZW92unff4UlcSh++lmqDWN3ZIq4w==
|
integrity sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg==
|
||||||
|
|
||||||
vue-eslint-parser@^9.3.0:
|
vue-eslint-parser@^9.3.0:
|
||||||
version "9.3.1"
|
version "9.3.1"
|
||||||
|
|||||||
6
node_modules/.package-lock.json
generated
vendored
Normal file
6
node_modules/.package-lock.json
generated
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"name": "App Builder for kintone",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {}
|
||||||
|
}
|
||||||
11
node_modules/.yarn-integrity
generated
vendored
11
node_modules/.yarn-integrity
generated
vendored
@@ -1,8 +1,13 @@
|
|||||||
{
|
{
|
||||||
"systemParams": "win32-x64-115",
|
"systemParams": "win32-x64-108",
|
||||||
"modulesFolders": [],
|
"modulesFolders": [
|
||||||
|
"node_modules"
|
||||||
|
],
|
||||||
"flags": [],
|
"flags": [],
|
||||||
"linkedModules": [],
|
"linkedModules": [
|
||||||
|
"@quasar\\quasar-ui-qactivity",
|
||||||
|
"docs"
|
||||||
|
],
|
||||||
"topLevelPatterns": [],
|
"topLevelPatterns": [],
|
||||||
"lockfileEntries": {},
|
"lockfileEntries": {},
|
||||||
"files": [],
|
"files": [],
|
||||||
|
|||||||
2
plugin/kintone-addins/.env.dev
Normal file
2
plugin/kintone-addins/.env.dev
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_SOURCE_MAP = inline
|
||||||
|
VITE_PORT = 4173
|
||||||
2
plugin/kintone-addins/.env.production
Normal file
2
plugin/kintone-addins/.env.production
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_SOURCE_MAP = false
|
||||||
|
VITE_PORT = 4173
|
||||||
@@ -4,22 +4,35 @@
|
|||||||
"version": "0.0.0",
|
"version": "0.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "tsc && set \"SOURCE_MAP=true\" && vite build && vite preview",
|
"dev": "run-p watch server ngrok",
|
||||||
"build": "tsc && vite build && xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
|
"watch": "vite build --watch --mode dev",
|
||||||
"build:dev": "tsc && set \"SOURCE_MAP=true\" && vite build && xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
|
"server": "vite dev --mode dev",
|
||||||
"preview": "vite preview",
|
"ngrok": "ngrok http 4173",
|
||||||
"ngrok":"ngrok http http://localhost:4173/",
|
"build": "run-s b:production copyjs:windows copycss:windows",
|
||||||
"vite":"vite dev"
|
"build:dev": "run-s b:dev copyjs:windows copycss:windows",
|
||||||
|
"build:linux": "run-s b:production copyjs:linux copycss:linux",
|
||||||
|
"build:linux-dev": "run-s b:dev copy:linux",
|
||||||
|
"b:production": "tsc & vite build --mode production",
|
||||||
|
"b:dev": "tsc & vite build --mode dev",
|
||||||
|
"copyjs:windows": "xcopy dist\\*.js ..\\..\\backend\\Temp\\ /E /I /Y",
|
||||||
|
"copyjs:linux": "cp -ur dist/*.js ../../backend/Temp",
|
||||||
|
"copycss:windows": "xcopy dist\\*.css ..\\..\\backend\\Temp\\ /E /I /Y",
|
||||||
|
"copycss:linux": "cp -ur dist/*.css ../../backend/Temp"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/jquery": "^3.5.24",
|
"@types/jquery": "^3.5.24",
|
||||||
"@types/node": "^20.8.9",
|
"@types/node": "^20.8.9",
|
||||||
|
"npm-run-all2": "^6.2.0",
|
||||||
"sass": "^1.69.5",
|
"sass": "^1.69.5",
|
||||||
"typescript": "^5.0.2",
|
"typescript": "^5.0.2",
|
||||||
"vite": "^4.4.5"
|
"vite": "^4.4.5",
|
||||||
|
"vite-plugin-checker": "^0.6.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"jquery": "^3.7.1",
|
"@kintone/rest-api-client": "^5.5.2",
|
||||||
"yarn": "^1.22.22"
|
"@popperjs/core": "^2.11.8",
|
||||||
|
"@types/bootstrap": "^5.2.10",
|
||||||
|
"bootstrap": "^5.3.3",
|
||||||
|
"jquery": "^3.7.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,10 @@
|
|||||||
| placeholder | 対象項目を選択してください| 入力フィールドに表示されるプレースホルダーのテキストです。この場合は設定されていません。 |
|
| placeholder | 対象項目を選択してください| 入力フィールドに表示されるプレースホルダーのテキストです。この場合は設定されていません。 |
|
||||||
| hint | 説明文| 長い説明文を設定することが可能です。(markdown形式サポート予定、現在HTML可能) |
|
| hint | 説明文| 長い説明文を設定することが可能です。(markdown形式サポート予定、現在HTML可能) |
|
||||||
| selectType |`single` or `multiple`| フィールド選択・他のアプリのフィールド選択の選択モードを設定する |
|
| selectType |`single` or `multiple`| フィールド選択・他のアプリのフィールド選択の選択モードを設定する |
|
||||||
|
| required | boolean | 必須チェックするかどうか |
|
||||||
|
| requiredMessage| string | 必須チェック時のエラーメッセージ。(未設定の場合「XXXX」が必須です。になります) |
|
||||||
|
| rules |"[val=>val<=100 && val>=1 \|\| '1-100の範囲内の数値を入力してください']"| 必須チェック以外のルールを設定する |
|
||||||
|
| fieldTypes |["SINGLE_LINE_TEXT","MULTI_LINE_TEXT","NUMBER"]| FieldInput,AppFieldSelectのみ使用可能。 |
|
||||||
|
|
||||||
|
|
||||||
### 使用可能なコンポーネント
|
### 使用可能なコンポーネント
|
||||||
@@ -78,14 +81,50 @@
|
|||||||
|-----|------------------|------------------|-----------------------------------------|
|
|-----|------------------|------------------|-----------------------------------------|
|
||||||
| 1 | テキストボックス | InputText | 一行のテキスト入力が可能なフィールドです。 |
|
| 1 | テキストボックス | InputText | 一行のテキスト入力が可能なフィールドです。 |
|
||||||
| 2 | テキストボックス(改行可能) | MuiltInputText | 複数行のテキスト入力が可能なテキストエリアです。 |
|
| 2 | テキストボックス(改行可能) | MuiltInputText | 複数行のテキスト入力が可能なテキストエリアです。 |
|
||||||
| 3 | 日付 | DatePicker | 日付を選択するためのカレンダーコンポーネントです。 |
|
| 3 | 数値入力 | NumInput | 数値のみ入力可能フィールド。 |
|
||||||
| 4 | フィールド選択 | FieldInput | システムのフィールドを選択するための入力コンポーネントです。 |
|
| 4 | 日付 | DatePicker | 日付を選択するためのカレンダーコンポーネントです。 |
|
||||||
| 5 | 選択リスト | SelectBox | 複数のオプションから選択するためのドロップダウンリストです。 |
|
| 5 | フィールド選択 | FieldInput | システムのフィールドを選択するための入力コンポーネントです。 |
|
||||||
| 6 | 条件式設定 | ConditionInput | 条件式やロジックを入力するためのコンポーネントです。 |
|
| 6 | 選択リスト | SelectBox | 複数のオプションから選択するためのドロップダウンリストです。 |
|
||||||
| 7 | イベント設定 |EventSetter | ボタンイベント設定のコンポーネントです。 |
|
| 7 | 条件式設定 | ConditionInput | 条件式やロジックを入力するためのコンポーネントです。 |
|
||||||
| 8 | 色選択 | ColorPicker | 色を設定する(追加予定中) |
|
| 8 | イベント設定 |EventSetter | ボタンイベント設定のコンポーネントです。 |
|
||||||
| 9 | 他のアプリのフィールド選択 | AppFieldPicker | 他のアプリのフィールドを選択する(追加予定中) |
|
| 9 | 色選択 | ColorPicker | 色を設定する |
|
||||||
| 10 |ユーザー選択 | UserPicker | ユーザーを選択する(追加予定中) |
|
| 10 | 他のアプリのフィールド選択 | AppFieldSelect | 他のアプリのフィールドを選択する |
|
||||||
|
| 11 | アプリ選択 | AppSelect | アプリを選択する |
|
||||||
|
|
||||||
|
### フィールド選択コンポーネントのfieldTypes属性を使用可能フィールド種別
|
||||||
|
| 番号 | 項目タイプ名 | 種別タイプ |
|
||||||
|
|------|-----------------------|-------------------|
|
||||||
|
| 1 | カテゴリー | CATEGORY |
|
||||||
|
| 2 | 作成日時 | CREATED_TIME |
|
||||||
|
| 3 | 作成者 | CREATOR |
|
||||||
|
| 4 | 更新者 | MODIFIER |
|
||||||
|
| 5 | レコード番号 | RECORD_NUMBER |
|
||||||
|
| 6 | 更新日時 | UPDATED_TIME |
|
||||||
|
| 7 | 計算 | CALC |
|
||||||
|
| 8 | チェックボックス | CHECK_BOX |
|
||||||
|
| 9 | 日付 | DATE |
|
||||||
|
| 10 | 日時 | DATETIME |
|
||||||
|
| 11 | ドロップダウン | DROP_DOWN |
|
||||||
|
| 12 | 添付ファイル | FILE |
|
||||||
|
| 13 | グループ | GROUP |
|
||||||
|
| 14 | グループ選択 | GROUP_SELECT |
|
||||||
|
| 15 | リンク | LINK |
|
||||||
|
| 16 | 文字列 (複数行) | MULTI_LINE_TEXT |
|
||||||
|
| 17 | 複数選択 | MULTI_SELECT |
|
||||||
|
| 18 | 数値 | NUMBER |
|
||||||
|
| 19 | 組織選択 | ORGANIZATION_SELECT |
|
||||||
|
| 20 | ラジオボタン | RADIO_BUTTON |
|
||||||
|
| 21 | 関連レコード一覧 | REFERENCE_TABLE |
|
||||||
|
| 22 | リッチエディター | RICH_TEXT |
|
||||||
|
| 23 | 文字列 (1行) | SINGLE_LINE_TEXT |
|
||||||
|
| 24 | ステータス | STATUS |
|
||||||
|
| 25 | 作業者 | STATUS_ASSIGNEE |
|
||||||
|
| 26 | テーブル | SUBTABLE |
|
||||||
|
| 27 | 時刻 | TIME |
|
||||||
|
| 28 | ユーザー選択 | USER_SELECT |
|
||||||
|
| 29 | スペース | SPACER |
|
||||||
|
| 30 | ルックアップ | lookup |
|
||||||
|
|
||||||
|
|
||||||
## 2.アクションアドインの開発
|
## 2.アクションアドインの開発
|
||||||
|
|
||||||
@@ -270,7 +309,7 @@ npm run build:dev
|
|||||||
- Azure App Service 拡張機能でデプロイが完了したことを確認します。
|
- Azure App Service 拡張機能でデプロイが完了したことを確認します。
|
||||||
- ka-addin の URL にアクセスしてアプリケーションが正常に動作しているか確認します。
|
- ka-addin の URL にアクセスしてアプリケーションが正常に動作しているか確認します。
|
||||||
|
|
||||||
3. **ローカルでプラグインをテストする**
|
3. **ローカルでプラグインをテストする(ZCCの導入ため、廃止する)**
|
||||||
1. kintone-addinsをPreviewで起動する
|
1. kintone-addinsをPreviewで起動する
|
||||||
```bash
|
```bash
|
||||||
yarn build:dev
|
yarn build:dev
|
||||||
@@ -278,7 +317,7 @@ yarn preview
|
|||||||
#またはyarn devは yarn build:dev + yarn preview と同じです
|
#またはyarn devは yarn build:dev + yarn preview と同じです
|
||||||
yarn dev
|
yarn dev
|
||||||
```
|
```
|
||||||
2. **ngrokをインストールする**
|
2. **ngrokをインストールする(ZCCの導入ため、廃止する)**
|
||||||
1. [ngrok の公式ウェブサイト](https://ngrok.com/)にアクセスします。
|
1. [ngrok の公式ウェブサイト](https://ngrok.com/)にアクセスします。
|
||||||
2. 「Sign up」をクリックしてアカウントを登録するか、既存のアカウントにログインします。
|
2. 「Sign up」をクリックしてアカウントを登録するか、既存のアカウントにログインします。
|
||||||
3. 登録またはログイン後、ダッシュボードに進み、ダウンロードリンクが表示されます。操作システム(Windows、macOS、Linux)に応じて、適切なバージョンを選択してダウンロードします。
|
3. 登録またはログイン後、ダッシュボードに進み、ダウンロードリンクが表示されます。操作システム(Windows、macOS、Linux)に応じて、適切なバージョンを選択してダウンロードします。
|
||||||
@@ -292,4 +331,10 @@ yarn dev
|
|||||||
6. ngrok を起動する
|
6. ngrok を起動する
|
||||||
```bash
|
```bash
|
||||||
ngrok https http://localhost:4173/
|
ngrok https http://localhost:4173/
|
||||||
```
|
```
|
||||||
|
3. kintone-addinsをビルドする
|
||||||
|
```bash
|
||||||
|
yarn build:dev #開発モード
|
||||||
|
#またはyarn devは yarn build:dev + yarn preview と同じです
|
||||||
|
yarn build #本番リリースモード
|
||||||
|
```
|
||||||
20
plugin/kintone-addins/src/actions/auto-lookup.scss
Normal file
20
plugin/kintone-addins/src/actions/auto-lookup.scss
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
.modal-backdrop {
|
||||||
|
--bs-backdrop-zindex: 1050;
|
||||||
|
--bs-backdrop-bg: #000;
|
||||||
|
--bs-backdrop-opacity: .5;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
z-index: var(--bs-backdrop-zindex);
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background-color: var(--bs-backdrop-bg)
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop.fade {
|
||||||
|
opacity: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
.modal-backdrop.show {
|
||||||
|
opacity: var(--bs-backdrop-opacity)
|
||||||
|
}
|
||||||
311
plugin/kintone-addins/src/actions/auto-lookup.ts
Normal file
311
plugin/kintone-addins/src/actions/auto-lookup.ts
Normal file
@@ -0,0 +1,311 @@
|
|||||||
|
import {
|
||||||
|
IAction,
|
||||||
|
IActionResult,
|
||||||
|
IActionNode,
|
||||||
|
IActionProperty,
|
||||||
|
IContext,
|
||||||
|
IField,
|
||||||
|
} from "../types/ActionTypes";
|
||||||
|
|
||||||
|
import { actionAddins } from ".";
|
||||||
|
import type { Record} from "@kintone/rest-api-client/lib/src/client/types";
|
||||||
|
import { KintoneAllRecordsError, KintoneRestAPIClient} from "@kintone/rest-api-client";
|
||||||
|
import "./auto-lookup.scss";
|
||||||
|
import "bootstrap/js/dist/modal";
|
||||||
|
// import "bootstrap/js/dist/spinner";
|
||||||
|
import {Modal} from "bootstrap"
|
||||||
|
import $ from "jquery";
|
||||||
|
|
||||||
|
interface IAutoLookUpProps {
|
||||||
|
displayName: string;
|
||||||
|
lookupField: LookupField;
|
||||||
|
condition: Condition;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Condition {
|
||||||
|
queryString: string;
|
||||||
|
index: number;
|
||||||
|
type: string;
|
||||||
|
children: Child[];
|
||||||
|
parent: null;
|
||||||
|
logicalOperator: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Child {
|
||||||
|
index: number;
|
||||||
|
type: string;
|
||||||
|
parent: string;
|
||||||
|
object: any;
|
||||||
|
operator: string;
|
||||||
|
value: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface LookupField {
|
||||||
|
app: App;
|
||||||
|
fields: Field[];
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Field {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
code: string;
|
||||||
|
label: string;
|
||||||
|
noLabel: boolean;
|
||||||
|
required: boolean;
|
||||||
|
lookup: Lookup;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface Lookup {
|
||||||
|
relatedApp: RelatedApp;
|
||||||
|
relatedKeyField: string;
|
||||||
|
fieldMappings: FieldMapping[];
|
||||||
|
lookupPickerFields: any[];
|
||||||
|
filterCond: string;
|
||||||
|
sort: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface FieldMapping {
|
||||||
|
field: string;
|
||||||
|
relatedField: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface RelatedApp {
|
||||||
|
app: string;
|
||||||
|
code: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface App {
|
||||||
|
id: string;
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
createdate: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class AutoLookUpAction implements IAction {
|
||||||
|
name: string;
|
||||||
|
actionProps: IActionProperty[];
|
||||||
|
props: IAutoLookUpProps;
|
||||||
|
constructor() {
|
||||||
|
this.name = "ルックアップ更新";
|
||||||
|
this.actionProps = [];
|
||||||
|
this.props = {} as IAutoLookUpProps;
|
||||||
|
this.register();
|
||||||
|
}
|
||||||
|
|
||||||
|
/***
|
||||||
|
* アクセスのメインの処理関数
|
||||||
|
*/
|
||||||
|
async process(
|
||||||
|
actionNode: IActionNode,
|
||||||
|
event: any,
|
||||||
|
context: IContext
|
||||||
|
): Promise<IActionResult> {
|
||||||
|
this.actionProps = actionNode.actionProps;
|
||||||
|
this.props = {
|
||||||
|
...actionNode.ActionValue,
|
||||||
|
condition: JSON.parse((actionNode.ActionValue as any).condition),
|
||||||
|
} as IAutoLookUpProps;
|
||||||
|
// console.log(context);
|
||||||
|
|
||||||
|
let result = {
|
||||||
|
canNext: true,
|
||||||
|
result: "",
|
||||||
|
} as IActionResult;
|
||||||
|
try {
|
||||||
|
const lookUpFields = this.props.lookupField.fields.filter(
|
||||||
|
(f) => f.lookup && f.lookup.relatedApp.app === String(kintone.app.getId())
|
||||||
|
);
|
||||||
|
if (!lookUpFields || lookUpFields.length===0) {
|
||||||
|
throw new Error(
|
||||||
|
`ルックアップの設定は不正です。${this.props.lookupField.fields[0].label} `
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const lookUpField = this.props.lookupField.fields[0];
|
||||||
|
const key = event.record[lookUpField.lookup.relatedKeyField].value;
|
||||||
|
const targetRecords = await this.getUpdateRecords(lookUpField, key);
|
||||||
|
//更新対象がない時にスキップ
|
||||||
|
if(targetRecords.length===0){
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
const updateRecords = this.convertForLookup(targetRecords,lookUpField,key);
|
||||||
|
console.log("updateRecords", updateRecords);
|
||||||
|
this.showSpinnerModel(this.props.lookupField.app,lookUpField);
|
||||||
|
const updateResult = await this.updateLookupTarget(updateRecords);
|
||||||
|
if(updateResult){
|
||||||
|
this.showResult(this.props.lookupField.app,lookUpField,updateRecords.length);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.closeDialog();
|
||||||
|
context.errors.handleError(error,actionNode,"ルックアップ更新中例外が発生しました");
|
||||||
|
result.canNext = false;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* REST API用クエリ作成
|
||||||
|
* TODO:共通関数として作成
|
||||||
|
* @param lookUpField
|
||||||
|
* @param key
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
makeQuery=(lookUpField:Field,key:any)=>{
|
||||||
|
let query ="";
|
||||||
|
if(typeof key==='number'){
|
||||||
|
query = `${lookUpField.code} = ${key}`
|
||||||
|
}
|
||||||
|
if(typeof key==='string'){
|
||||||
|
query = `${lookUpField.code} = "${key}"`
|
||||||
|
}
|
||||||
|
if(this.props.condition.queryString!==''){
|
||||||
|
query = `${query} and (${this.props.condition.queryString})`
|
||||||
|
}
|
||||||
|
return query;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新対象のレコードを取得する
|
||||||
|
*/
|
||||||
|
getUpdateRecords = async (lookUpField:Field,key:any):Promise< Record[]>=>{
|
||||||
|
const client=new KintoneRestAPIClient();
|
||||||
|
const resp = await client.record.getAllRecords({
|
||||||
|
app:this.props.lookupField.app.id,
|
||||||
|
fields:["$id"],
|
||||||
|
condition:this.makeQuery(lookUpField,key)
|
||||||
|
});
|
||||||
|
return resp;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ルックアップ更新用レコードに変換する
|
||||||
|
* @param targetRecords 更新対象レコード
|
||||||
|
* @param lookUpField ルックアップフィールド
|
||||||
|
* @param key ルックアップフィールドの値
|
||||||
|
* @returns
|
||||||
|
*/
|
||||||
|
convertForLookup = (targetRecords:Record[],lookUpField:Field,key:any):Array<any>=>{
|
||||||
|
return targetRecords.map((r) => ({
|
||||||
|
id: Number(r["$id"].value),
|
||||||
|
record: { [lookUpField.code]: { value: key } },
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ルックアップ先を更新する
|
||||||
|
* @param updateRecords
|
||||||
|
*/
|
||||||
|
updateLookupTarget = async (updateRecords:Array<any>):Promise<boolean>=>{
|
||||||
|
if (updateRecords && updateRecords.length > 0) {
|
||||||
|
try{
|
||||||
|
const client=new KintoneRestAPIClient();
|
||||||
|
const result = await client.record.updateAllRecords({
|
||||||
|
app:this.props.lookupField.app.id,
|
||||||
|
records:updateRecords
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
}catch(error ){
|
||||||
|
if(error instanceof KintoneAllRecordsError){
|
||||||
|
this.showError(this.props.lookupField.app,
|
||||||
|
this.props.lookupField.fields[0],
|
||||||
|
error as KintoneAllRecordsError,updateRecords.length);
|
||||||
|
return false;
|
||||||
|
}else{
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// await kintone.api(kintone.api.url("/k/v1/records.json", true), "PUT", {
|
||||||
|
// app: this.props.lookupField.app.id,
|
||||||
|
// records: updateRecords
|
||||||
|
// });
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新中のダイアログ表示
|
||||||
|
* @param app
|
||||||
|
*/
|
||||||
|
showSpinnerModel = (app:App,lookup:Field) => {
|
||||||
|
let dialog = $("#alcLookupModal");
|
||||||
|
if(dialog.length===0){
|
||||||
|
const modalHTML = `<div class="bs-scope">
|
||||||
|
<div class="modal" id="alcLookupModal" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
|
||||||
|
<div class="modal-dialog-centered">
|
||||||
|
<div class="modal-dialog modal-content">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h1 class="modal-title fs-5" id="alcLookupModalLabel">ルックアップ同期処理</h1>
|
||||||
|
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
|
||||||
|
</div>
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="row" id="app${app.id}_${lookup.code}">
|
||||||
|
<div class="spinner-border text-secondary col-1 " role="alert"></div>
|
||||||
|
<div class="col">${app.name}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="modal-footer">
|
||||||
|
<button type="button" class="btn btn-primary" data-bs-dismiss="modal">OK</button>
|
||||||
|
</div>
|
||||||
|
</div></div></div></div>`;
|
||||||
|
$(modalHTML).appendTo("body");
|
||||||
|
dialog = $("#alcLookupModal");
|
||||||
|
dialog.get()[0].addEventListener('hidden.bs.modal',(ev)=>{
|
||||||
|
Modal.getOrCreateInstance(dialog.get()[0]).dispose();
|
||||||
|
$("#alcLookupModal").parent().remove();
|
||||||
|
});
|
||||||
|
}else{
|
||||||
|
const dialogBody=$("#alcLookupModal .modal-body");
|
||||||
|
const htmlrow=`
|
||||||
|
<div class="row" id="app${app.id}_${lookup.code}">
|
||||||
|
<div class="spinner-border text-secondary col-1 " role="alert">
|
||||||
|
</div>
|
||||||
|
<div class="col">${app.name}</div>
|
||||||
|
<div>`;
|
||||||
|
dialogBody.append(htmlrow);
|
||||||
|
}
|
||||||
|
Modal.getOrCreateInstance(dialog.get()[0]).show();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新結果を表示する
|
||||||
|
* @param app 更新先アプリ情報
|
||||||
|
* @param count 更新件数
|
||||||
|
*/
|
||||||
|
showResult=(app:App,lookup:Field,count:number)=>{
|
||||||
|
const dialogBody=$(`#alcLookupModal .modal-body #app${app.id}_${lookup.code}`);
|
||||||
|
const html=` <div class="col-1 text-success">✔</div>
|
||||||
|
<div class="col">${app.name}</div>
|
||||||
|
<div class="col">更新件数:${count}件</div>`;
|
||||||
|
dialogBody.html(html);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 更新結果を表示する
|
||||||
|
* @param app 更新先アプリ情報
|
||||||
|
* @param count 更新件数
|
||||||
|
*/
|
||||||
|
showError=(app:App,lookup:Field,error:KintoneAllRecordsError,allCount:Number)=>{
|
||||||
|
const message=error.error.message;
|
||||||
|
const proRecords = error.numOfProcessedRecords;
|
||||||
|
const allRecords=error.numOfAllRecords;
|
||||||
|
const dialogBody=$(`#alcLookupModal .modal-body #app${app.id}_${lookup.code}`);
|
||||||
|
const html=`<div class="col-1 text-danger">✖</div>
|
||||||
|
<div class="col">${app.name}</div>
|
||||||
|
<div class="col">更新件数:${proRecords}/${allRecords}</div>
|
||||||
|
<div class="row text-danger">${message}<div>`;
|
||||||
|
dialogBody.html(html);
|
||||||
|
}
|
||||||
|
/**
|
||||||
|
* ダイアログ画面を閉じる
|
||||||
|
*/
|
||||||
|
closeDialog=()=>{
|
||||||
|
const dialog = $("#alcLookupModal");
|
||||||
|
Modal.getOrCreateInstance(dialog.get()[0]).dispose();
|
||||||
|
$("#alcLookupModal").parent().remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
register(): void {
|
||||||
|
actionAddins[this.name] = this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new AutoLookUpAction();
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
|
|
||||||
import { actionAddins } from ".";
|
import { actionAddins } from ".";
|
||||||
import { IField, IAction,IActionResult, IActionNode, IActionProperty, IContext } from "../types/ActionTypes";
|
import { IField, IAction,IActionResult, IActionNode, IActionProperty, IContext, IVarName } from "../types/ActionTypes";
|
||||||
import { Formatter } from "../util/format";
|
import { Formatter } from "../util/format";
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
@@ -13,7 +13,7 @@ interface IAutoNumberingProps{
|
|||||||
format:string;
|
format:string;
|
||||||
prefix:string;
|
prefix:string;
|
||||||
suffix:string;
|
suffix:string;
|
||||||
verName:string;
|
verName:IVarName;
|
||||||
}
|
}
|
||||||
|
|
||||||
export class AutoNumbering implements IAction{
|
export class AutoNumbering implements IAction{
|
||||||
@@ -29,7 +29,7 @@ export class AutoNumbering implements IAction{
|
|||||||
format:'',
|
format:'',
|
||||||
prefix:'',
|
prefix:'',
|
||||||
suffix:'',
|
suffix:'',
|
||||||
verName:''
|
verName:{name:''}
|
||||||
}
|
}
|
||||||
globalThis.window.$format=this.format;
|
globalThis.window.$format=this.format;
|
||||||
this.register();
|
this.register();
|
||||||
@@ -56,8 +56,8 @@ export class AutoNumbering implements IAction{
|
|||||||
const docNum = await this.createNumber(this.props);
|
const docNum = await this.createNumber(this.props);
|
||||||
record[this.props.field.code].value=docNum;
|
record[this.props.field.code].value=docNum;
|
||||||
//変数設定
|
//変数設定
|
||||||
if(this.props.verName){
|
if(this.props.verName && this.props.verName.name!==''){
|
||||||
context.variables[this.props.verName]=docNum;
|
context.variables[this.props.verName.name]=docNum;
|
||||||
}
|
}
|
||||||
result= {
|
result= {
|
||||||
canNext:true,
|
canNext:true,
|
||||||
@@ -65,8 +65,7 @@ export class AutoNumbering implements IAction{
|
|||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}catch(error){
|
}catch(error){
|
||||||
console.error(error);
|
context.errors.handleError(error,actionNode);
|
||||||
event.error="処理中異常が発生しました。";
|
|
||||||
return {
|
return {
|
||||||
canNext:false,
|
canNext:false,
|
||||||
result:false
|
result:false
|
||||||
@@ -84,6 +83,7 @@ export class AutoNumbering implements IAction{
|
|||||||
|
|
||||||
execEval(match:string,expr:string):string{
|
execEval(match:string,expr:string):string{
|
||||||
console.log(match);
|
console.log(match);
|
||||||
|
// @ts-ignore
|
||||||
return eval(expr);
|
return eval(expr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user